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

No files matched your search

+80 -28
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_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 = [ CODE_INTERPRETER_BLOCKED_MODULES = [
library.strip() for library in os.getenv('CODE_INTERPRETER_BLOCKED_MODULES', '').split(',') if library.strip() 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_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' 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 = 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' 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))) 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', '') 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_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' 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', '') 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' 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_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', '') 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', '') 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', '') 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_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', '') 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_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', '') 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' os.getenv('USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
) )
USER_PERMISSIONS_FOLDERS_ALLOW_SHARING = ( USER_PERMISSIONS_FOLDERS_ALLOW_SHARING = os.getenv('USER_PERMISSIONS_FOLDERS_ALLOW_SHARING', 'False').lower() == 'true'
os.getenv('USER_PERMISSIONS_FOLDERS_ALLOW_SHARING', 'False').lower() == 'true'
)
USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING = ( 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 = 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', '')) 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_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') 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_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(',')] 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', 'sub_claim': 'id',
} }
if ( if OAUTH_CLIENT_ID and (OAUTH_CLIENT_SECRET or OAUTH_CODE_CHALLENGE_METHOD) and OPENID_PROVIDER_URL:
OAUTH_CLIENT_ID
and (OAUTH_CLIENT_SECRET or OAUTH_CODE_CHALLENGE_METHOD)
and OPENID_PROVIDER_URL
):
def oidc_oauth_register(oauth: OAuth): def oidc_oauth_register(oauth: OAuth):
client_kwargs = { client_kwargs = {
+1 -3
View File
@@ -599,9 +599,7 @@ except (ValueError, TypeError):
# SSL verification for tool server connections specifically. # SSL verification for tool server connections specifically.
# Accepts "True", "False", or a path to a CA bundle file. # Accepts "True", "False", or a path to a CA bundle file.
# When "True", falls back to AIOHTTP_CLIENT_SSL_CERT_FILE if set. # When "True", falls back to AIOHTTP_CLIENT_SSL_CERT_FILE if set.
AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = _parse_ssl_env( AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = _parse_ssl_env(os.getenv('AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL', 'True'))
os.getenv('AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL', 'True')
)
AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = os.getenv('AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER', '') AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = os.getenv('AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER', '')
+579 -179
View File
@@ -42,177 +42,581 @@ class EventDefinition(BaseModel):
class EventDefinitions(BaseModel): class EventDefinitions(BaseModel):
model_config = ConfigDict(frozen=True) model_config = ConfigDict(frozen=True)
SYSTEM_STARTUP_STARTED: EventDefinition = EventDefinition(name='system.startup.started', description='Application startup began.', message='Startup started') SYSTEM_STARTUP_STARTED: EventDefinition = EventDefinition(
SYSTEM_STARTUP_COMPLETED: EventDefinition = EventDefinition(name='system.startup.completed', description='Application startup completed.', message='Startup completed') name='system.startup.started', description='Application startup began.', message='Startup started'
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') SYSTEM_STARTUP_COMPLETED: EventDefinition = EventDefinition(
CONFIG_IMPORTED: EventDefinition = EventDefinition(name='config.imported', description='Configuration was imported.', message='Config imported') name='system.startup.completed', description='Application startup completed.', message='Startup completed'
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') SYSTEM_SHUTDOWN_STARTED: EventDefinition = EventDefinition(
CONFIG_CONNECTIONS_UPDATED: EventDefinition = EventDefinition(name='config.connections.updated', description='Connection configuration was updated.', message='Config Connections updated') name='system.shutdown.started', description='Application shutdown began.', message='Shutdown started'
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') SYSTEM_SHUTDOWN_COMPLETED: EventDefinition = EventDefinition(
CONFIG_CODE_EXECUTION_UPDATED: EventDefinition = EventDefinition(name='config.code_execution.updated', description='Code execution configuration was updated.', message='Config Code Execution updated') name='system.shutdown.completed', description='Application shutdown completed.', message='Shutdown completed'
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_IMPORTED: EventDefinition = EventDefinition(
CONFIG_SUGGESTIONS_UPDATED: EventDefinition = EventDefinition(name='config.suggestions.updated', description='Suggestion configuration was updated.', message='Config Suggestions updated') name='config.imported', description='Configuration was imported.', message='Config imported'
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') CONFIG_UPDATED: EventDefinition = EventDefinition(
AUTH_LOGOUT: EventDefinition = EventDefinition(name='auth.logout', description='A user logged out.', message='User logged out') name='config.updated', description='Configuration was updated.', message='Config updated'
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') CONFIG_WEBHOOK_UPDATED: EventDefinition = EventDefinition(
AUTH_API_KEY_DELETED: EventDefinition = EventDefinition(name='auth.api_key.deleted', description='A user API key was deleted.', message='API key deleted') name='config.webhook.updated',
AUTH_OAUTH_SESSION_DELETED: EventDefinition = EventDefinition(name='auth.oauth_session.deleted', description='An OAuth session was deleted.', message='OAuth session deleted') description='Event webhook configuration was updated.',
USER_CREATED: EventDefinition = EventDefinition(name='user.created', description='A user account was created.', message='User created') message='Webhook configuration updated',
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') CONFIG_CONNECTIONS_UPDATED: EventDefinition = EventDefinition(
USER_ROLE_UPDATED: EventDefinition = EventDefinition(name='user.role_updated', description='A user role was updated.', message='User role updated') name='config.connections.updated',
USER_STATUS_UPDATED: EventDefinition = EventDefinition(name='user.status_updated', description='A user status was updated.', message='User status updated') description='Connection configuration was updated.',
USER_SETTINGS_UPDATED: EventDefinition = EventDefinition(name='user.settings_updated', description='A user settings object was updated.', message='User settings updated') message='Config Connections 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') CONFIG_TOOL_SERVERS_UPDATED: EventDefinition = EventDefinition(
GROUP_CREATED: EventDefinition = EventDefinition(name='group.created', description='A group was created.', message='Group created') name='config.tool_servers.updated',
GROUP_UPDATED: EventDefinition = EventDefinition(name='group.updated', description='A group was updated.', message='Group updated') description='Tool server configuration was updated.',
GROUP_DELETED: EventDefinition = EventDefinition(name='group.deleted', description='A group was deleted.', message='Group deleted') message='Config Tool Servers updated',
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') CONFIG_TERMINAL_SERVERS_UPDATED: EventDefinition = EventDefinition(
CHAT_CREATED: EventDefinition = EventDefinition(name='chat.created', description='A chat was created.', message='Chat created') name='config.terminal_servers.updated',
CHAT_IMPORTED: EventDefinition = EventDefinition(name='chat.imported', description='A chat was imported.', message='Chat imported') description='Terminal server configuration was updated.',
CHAT_UPDATED: EventDefinition = EventDefinition(name='chat.updated', description='A chat was updated.', message='Chat updated') message='Config Terminal Servers 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') CONFIG_CODE_EXECUTION_UPDATED: EventDefinition = EventDefinition(
CHAT_COMPACTED: EventDefinition = EventDefinition(name='chat.compacted', description='A chat was compacted.', message='Chat compacted') name='config.code_execution.updated',
CHAT_PINNED: EventDefinition = EventDefinition(name='chat.pinned', description='A chat was pinned.', message='Chat pinned') description='Code execution configuration was updated.',
CHAT_UNPINNED: EventDefinition = EventDefinition(name='chat.unpinned', description='A chat was unpinned.', message='Chat unpinned') message='Config Code Execution updated',
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') CONFIG_MODELS_UPDATED: EventDefinition = EventDefinition(
CHAT_UNARCHIVED: EventDefinition = EventDefinition(name='chat.unarchived', description='A chat was unarchived.', message='Chat unarchived') name='config.models.updated', description='Model configuration was updated.', message='Config Models updated'
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') CONFIG_BANNERS_UPDATED: EventDefinition = EventDefinition(
CHAT_FOLDER_UPDATED: EventDefinition = EventDefinition(name='chat.folder_updated', description='A chat folder assignment was updated.', message='Chat folder updated') name='config.banners.updated', description='Banner configuration was updated.', message='Config Banners 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') CONFIG_SUGGESTIONS_UPDATED: EventDefinition = EventDefinition(
MESSAGE_CREATED: EventDefinition = EventDefinition(name='message.created', description='A message was created.', message='Message created') name='config.suggestions.updated',
MESSAGE_UPDATED: EventDefinition = EventDefinition(name='message.updated', description='A message was updated.', message='Message updated') description='Suggestion configuration was updated.',
MESSAGE_DELETED: EventDefinition = EventDefinition(name='message.deleted', description='A message was deleted.', message='Message deleted') message='Config Suggestions updated',
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') AUTH_SIGNUP: EventDefinition = EventDefinition(
MESSAGE_REACTION_REMOVED: EventDefinition = EventDefinition(name='message.reaction_removed', description='A reaction was removed from a message.', message='Message reaction removed') name='auth.signup', description='A user account was created through signup.', message='User signed up'
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') AUTH_LOGIN: EventDefinition = EventDefinition(
CHANNEL_CREATED: EventDefinition = EventDefinition(name='channel.created', description='A channel was created.', message='Channel created') name='auth.login', description='A user successfully logged in.', message='User logged in'
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') AUTH_LOGOUT: EventDefinition = EventDefinition(
CHANNEL_MEMBER_ADDED: EventDefinition = EventDefinition(name='channel.member_added', description='A member was added to a channel.', message='Channel member added') name='auth.logout', description='A user logged out.', message='User logged out'
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') AUTH_PASSWORD_CHANGED: EventDefinition = EventDefinition(
CHANNEL_WEBHOOK_CREATED: EventDefinition = EventDefinition(name='channel.webhook.created', description='A channel incoming webhook was created.', message='Channel Webhook created') name='auth.password_changed', description='A user password was changed.', message='Password changed'
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') AUTH_API_KEY_CREATED: EventDefinition = EventDefinition(
FILE_UPLOADED: EventDefinition = EventDefinition(name='file.uploaded', description='A file was uploaded.', message='File uploaded') name='auth.api_key.created', description='A user API key was created.', message='API key created'
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') AUTH_API_KEY_DELETED: EventDefinition = EventDefinition(
FILE_DELETED: EventDefinition = EventDefinition(name='file.deleted', description='A file was deleted.', message='File deleted') name='auth.api_key.deleted', description='A user API key was deleted.', message='API key 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') AUTH_OAUTH_SESSION_DELETED: EventDefinition = EventDefinition(
FOLDER_UPDATED: EventDefinition = EventDefinition(name='folder.updated', description='A folder was updated.', message='Folder updated') name='auth.oauth_session.deleted', description='An OAuth session was deleted.', message='OAuth session deleted'
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') USER_CREATED: EventDefinition = EventDefinition(
FOLDER_DELETED: EventDefinition = EventDefinition(name='folder.deleted', description='A folder was deleted.', message='Folder deleted') name='user.created', description='A user account was created.', message='User created'
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') USER_UPDATED: EventDefinition = EventDefinition(
NOTE_ACCESS_UPDATED: EventDefinition = EventDefinition(name='note.access_updated', description='Note access was updated.', message='Note access updated') name='user.updated', description='A user account was updated.', message='User 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') USER_DELETED: EventDefinition = EventDefinition(
NOTE_DELETED: EventDefinition = EventDefinition(name='note.deleted', description='A note was deleted.', message='Note deleted') name='user.deleted', description='A user account was deleted.', message='User 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') USER_ROLE_UPDATED: EventDefinition = EventDefinition(
MEMORY_DELETED: EventDefinition = EventDefinition(name='memory.deleted', description='A memory was deleted.', message='Memory deleted') name='user.role_updated', description='A user role was updated.', message='User role updated'
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') USER_STATUS_UPDATED: EventDefinition = EventDefinition(
KNOWLEDGE_UPDATED: EventDefinition = EventDefinition(name='knowledge.updated', description='A knowledge was updated.', message='Knowledge updated') name='user.status_updated', description='A user status was updated.', message='User status 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') USER_SETTINGS_UPDATED: EventDefinition = EventDefinition(
KNOWLEDGE_REINDEXED: EventDefinition = EventDefinition(name='knowledge.reindexed', description='A knowledge was reindexed.', message='Knowledge reindexed') name='user.settings_updated', description='A user settings object was updated.', message='User settings updated'
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') USER_PROFILE_UPDATED: EventDefinition = EventDefinition(
KNOWLEDGE_FILE_UPDATED: EventDefinition = EventDefinition(name='knowledge.file.updated', description='A knowledge file was updated.', message='Knowledge File updated') name='user.profile_updated', description='A user profile was updated.', message='User profile 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') USER_PERMISSIONS_UPDATED: EventDefinition = EventDefinition(
KNOWLEDGE_DIRECTORY_CREATED: EventDefinition = EventDefinition(name='knowledge.directory.created', description='A knowledge directory was created.', message='Knowledge Directory created') name='user.permissions_updated',
KNOWLEDGE_DIRECTORY_UPDATED: EventDefinition = EventDefinition(name='knowledge.directory.updated', description='A knowledge directory was updated.', message='Knowledge Directory updated') description='A user permissions object was updated.',
KNOWLEDGE_DIRECTORY_DELETED: EventDefinition = EventDefinition(name='knowledge.directory.deleted', description='A knowledge directory was deleted.', message='Knowledge Directory deleted') message='User permissions updated',
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') GROUP_CREATED: EventDefinition = EventDefinition(
KNOWLEDGE_EXTERNAL_CONNECTION_DELETED: EventDefinition = EventDefinition(name='knowledge.external_connection.deleted', description='A knowledge external connection was deleted.', message='Knowledge External Connection deleted') name='group.created', description='A group was created.', message='Group created'
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') GROUP_UPDATED: EventDefinition = EventDefinition(
RETRIEVAL_VECTOR_DB_RESET: EventDefinition = EventDefinition(name='retrieval.vector_db.reset', description='The retrieval vector database was reset.', message='Retrieval Vector Db reset') name='group.updated', description='A group was updated.', message='Group updated'
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') GROUP_DELETED: EventDefinition = EventDefinition(
MODEL_IMPORTED: EventDefinition = EventDefinition(name='model.imported', description='A model was imported.', message='Model imported') name='group.deleted', description='A group was deleted.', message='Group deleted'
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') GROUP_MEMBER_ADDED: EventDefinition = EventDefinition(
MODEL_DELETED: EventDefinition = EventDefinition(name='model.deleted', description='A model was deleted.', message='Model deleted') name='group.member_added', description='A user was added to a group.', message='Group member added'
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') GROUP_MEMBER_REMOVED: EventDefinition = EventDefinition(
MODEL_ACCESS_UPDATED: EventDefinition = EventDefinition(name='model.access_updated', description='Model access was updated.', message='Model access updated') name='group.member_removed', description='A user was removed from a group.', message='Group member removed'
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') CHAT_CREATED: EventDefinition = EventDefinition(
MODEL_PROVIDER_MODEL_DELETED: EventDefinition = EventDefinition(name='model.provider_model.deleted', description='A provider model was deleted.', message='Provider model deleted') name='chat.created', description='A chat was created.', message='Chat created'
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') CHAT_IMPORTED: EventDefinition = EventDefinition(
FUNCTION_DELETED: EventDefinition = EventDefinition(name='function.deleted', description='A function was deleted.', message='Function deleted') name='chat.imported', description='A chat was imported.', message='Chat imported'
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') CHAT_UPDATED: EventDefinition = EventDefinition(
FUNCTION_VALVES_UPDATED: EventDefinition = EventDefinition(name='function.valves_updated', description='Function valves were updated.', message='Function valves updated') name='chat.updated', description='A chat was updated.', message='Chat 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') CHAT_DELETED: EventDefinition = EventDefinition(
TOOL_DELETED: EventDefinition = EventDefinition(name='tool.deleted', description='A tool was deleted.', message='Tool deleted') name='chat.deleted', description='A chat was deleted.', message='Chat 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') CHAT_DELETED_ALL: EventDefinition = EventDefinition(
SKILL_CREATED: EventDefinition = EventDefinition(name='skill.created', description='A skill was created.', message='Skill created') name='chat.deleted_all', description='All chats for a scope were deleted.', message='Chat deleted all'
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') CHAT_COMPACTED: EventDefinition = EventDefinition(
SKILL_ENABLED: EventDefinition = EventDefinition(name='skill.enabled', description='A skill was enabled.', message='Skill enabled') name='chat.compacted', description='A chat was compacted.', message='Chat compacted'
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') CHAT_PINNED: EventDefinition = EventDefinition(
PROMPT_UPDATED: EventDefinition = EventDefinition(name='prompt.updated', description='A prompt was updated.', message='Prompt updated') name='chat.pinned', description='A chat was pinned.', message='Chat pinned'
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') CHAT_UNPINNED: EventDefinition = EventDefinition(
PROMPT_DISABLED: EventDefinition = EventDefinition(name='prompt.disabled', description='A prompt was disabled.', message='Prompt disabled') name='chat.unpinned', description='A chat was unpinned.', message='Chat unpinned'
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') CHAT_CLONED: EventDefinition = EventDefinition(
PIPELINE_UPLOADED: EventDefinition = EventDefinition(name='pipeline.uploaded', description='A pipeline was uploaded.', message='Pipeline uploaded') name='chat.cloned', description='A chat was cloned.', message='Chat cloned'
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') CHAT_ARCHIVED: EventDefinition = EventDefinition(
PIPELINE_VALVES_UPDATED: EventDefinition = EventDefinition(name='pipeline.valves_updated', description='Pipeline valves were updated.', message='Pipeline valves updated') name='chat.archived', description='A chat was archived.', message='Chat archived'
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') CHAT_UNARCHIVED: EventDefinition = EventDefinition(
CALENDAR_DELETED: EventDefinition = EventDefinition(name='calendar.deleted', description='A calendar was deleted.', message='Calendar deleted') name='chat.unarchived', description='A chat was unarchived.', message='Chat unarchived'
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') CHAT_SHARED: EventDefinition = EventDefinition(
CALENDAR_EVENT_UPDATED: EventDefinition = EventDefinition(name='calendar.event.updated', description='A calendar event was updated.', message='Calendar Event updated') name='chat.shared', description='A chat was shared.', message='Chat shared'
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') CHAT_UNSHARED: EventDefinition = EventDefinition(
AUTOMATION_CREATED: EventDefinition = EventDefinition(name='automation.created', description='An automation was created.', message='Automation created') name='chat.unshared', description='A chat was unshared.', message='Chat unshared'
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') CHAT_FOLDER_UPDATED: EventDefinition = EventDefinition(
AUTOMATION_DISABLED: EventDefinition = EventDefinition(name='automation.disabled', description='An automation was disabled.', message='Automation disabled') name='chat.folder_updated', description='A chat folder assignment was updated.', message='Chat folder updated'
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') CHAT_TAG_ADDED: EventDefinition = EventDefinition(
AUTOMATION_RUN_COMPLETED: EventDefinition = EventDefinition(name='automation.run_completed', description='An automation run completed.', message='Automation run completed') name='chat.tag_added', description='A tag was added to a chat.', message='Chat tag added'
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') CHAT_TAG_REMOVED: EventDefinition = EventDefinition(
FEEDBACK_UPDATED: EventDefinition = EventDefinition(name='feedback.updated', description='A feedback was updated.', message='Feedback updated') name='chat.tag_removed', description='A tag was removed from a chat.', message='Chat tag removed'
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') MESSAGE_CREATED: EventDefinition = EventDefinition(
IMAGE_GENERATED: EventDefinition = EventDefinition(name='image.generated', description='An image was generated.', message='Image generated') name='message.created', description='A message was created.', message='Message created'
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') MESSAGE_UPDATED: EventDefinition = EventDefinition(
AUDIO_TRANSCRIPTION_REQUESTED: EventDefinition = EventDefinition(name='audio.transcription_requested', description='Audio transcription was requested.', message='Transcription requested') name='message.updated', description='A message was updated.', message='Message updated'
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') 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() EVENTS = EventDefinitions()
@@ -232,6 +636,7 @@ def get_event_catalog() -> list[dict[str, str]]:
for definition in EVENT_DEFINITIONS for definition in EVENT_DEFINITIONS
] ]
SENSITIVE_KEYS = { SENSITIVE_KEYS = {
'password', 'password',
'hashed_password', 'hashed_password',
@@ -369,10 +774,7 @@ async def event_target_matches(
from open_webui.models.groups import Groups from open_webui.models.groups import Groups
groups_by_user = await Groups.get_groups_by_member_ids(list(user_ids)) groups_by_user = await Groups.get_groups_by_member_ids(list(user_ids))
user_group_ids = { user_group_ids = {user_id: {group.id for group in groups} for user_id, groups in groups_by_user.items()}
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()) 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, subject=_sanitize(subject) if subject else None,
data=_sanitize(data or {}), data=_sanitize(data or {}),
message=message, message=message,
) )
async def dispatch_webhook_event(app: Any, event: Event) -> None: 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, '__app__': app,
'__request__': request, '__request__': request,
} }
params = { params = {key: value for key, value in extra_params.items() if accepts_kwargs or key in sig.parameters}
key: value for key, value in extra_params.items() if accepts_kwargs or key in sig.parameters
}
if inspect.iscoroutinefunction(handler): if inspect.iscoroutinefunction(handler):
await handler(**params) await handler(**params)
@@ -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. # Maps every dot-notation blob path to its legacy env/config key name.
# Built from the legacy persistent config declarations in config.py. # Built from the legacy persistent config declarations in config.py.
BLOB_PATH_TO_KEY = { BLOB_PATH_TO_KEY = {
"audio.stt.allowed_extensions": "AUDIO_STT_ALLOWED_EXTENSIONS", 'audio.stt.allowed_extensions': 'AUDIO_STT_ALLOWED_EXTENSIONS',
"audio.stt.azure.api_key": "AUDIO_STT_AZURE_API_KEY", 'audio.stt.azure.api_key': 'AUDIO_STT_AZURE_API_KEY',
"audio.stt.azure.base_url": "AUDIO_STT_AZURE_BASE_URL", 'audio.stt.azure.base_url': 'AUDIO_STT_AZURE_BASE_URL',
"audio.stt.azure.locales": "AUDIO_STT_AZURE_LOCALES", 'audio.stt.azure.locales': 'AUDIO_STT_AZURE_LOCALES',
"audio.stt.azure.max_speakers": "AUDIO_STT_AZURE_MAX_SPEAKERS", 'audio.stt.azure.max_speakers': 'AUDIO_STT_AZURE_MAX_SPEAKERS',
"audio.stt.azure.region": "AUDIO_STT_AZURE_REGION", 'audio.stt.azure.region': 'AUDIO_STT_AZURE_REGION',
"audio.stt.deepgram.api_key": "DEEPGRAM_API_KEY", 'audio.stt.deepgram.api_key': 'DEEPGRAM_API_KEY',
"audio.stt.engine": "AUDIO_STT_ENGINE", 'audio.stt.engine': 'AUDIO_STT_ENGINE',
"audio.stt.mistral.api_base_url": "AUDIO_STT_MISTRAL_API_BASE_URL", '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.api_key': 'AUDIO_STT_MISTRAL_API_KEY',
"audio.stt.mistral.use_chat_completions": "AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS", 'audio.stt.mistral.use_chat_completions': 'AUDIO_STT_MISTRAL_USE_CHAT_COMPLETIONS',
"audio.stt.model": "AUDIO_STT_MODEL", 'audio.stt.model': 'AUDIO_STT_MODEL',
"audio.stt.openai.api_base_url": "AUDIO_STT_OPENAI_API_BASE_URL", 'audio.stt.openai.api_base_url': 'AUDIO_STT_OPENAI_API_BASE_URL',
"audio.stt.openai.api_key": "AUDIO_STT_OPENAI_API_KEY", 'audio.stt.openai.api_key': 'AUDIO_STT_OPENAI_API_KEY',
"audio.stt.supported_content_types": "AUDIO_STT_SUPPORTED_CONTENT_TYPES", 'audio.stt.supported_content_types': 'AUDIO_STT_SUPPORTED_CONTENT_TYPES',
"audio.stt.whisper_model": "WHISPER_MODEL", 'audio.stt.whisper_model': 'WHISPER_MODEL',
"audio.tts.api_key": "AUDIO_TTS_API_KEY", 'audio.tts.api_key': 'AUDIO_TTS_API_KEY',
"audio.tts.azure.speech_base_url": "AUDIO_TTS_AZURE_SPEECH_BASE_URL", '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_output_format': 'AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT',
"audio.tts.azure.speech_region": "AUDIO_TTS_AZURE_SPEECH_REGION", 'audio.tts.azure.speech_region': 'AUDIO_TTS_AZURE_SPEECH_REGION',
"audio.tts.engine": "AUDIO_TTS_ENGINE", 'audio.tts.engine': 'AUDIO_TTS_ENGINE',
"audio.tts.mistral.api_base_url": "AUDIO_TTS_MISTRAL_API_BASE_URL", 'audio.tts.mistral.api_base_url': 'AUDIO_TTS_MISTRAL_API_BASE_URL',
"audio.tts.mistral.api_key": "AUDIO_TTS_MISTRAL_API_KEY", 'audio.tts.mistral.api_key': 'AUDIO_TTS_MISTRAL_API_KEY',
"audio.tts.model": "AUDIO_TTS_MODEL", 'audio.tts.model': 'AUDIO_TTS_MODEL',
"audio.tts.openai.api_base_url": "AUDIO_TTS_OPENAI_API_BASE_URL", '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.api_key': 'AUDIO_TTS_OPENAI_API_KEY',
"audio.tts.openai.params": "AUDIO_TTS_OPENAI_PARAMS", 'audio.tts.openai.params': 'AUDIO_TTS_OPENAI_PARAMS',
"audio.tts.split_on": "AUDIO_TTS_SPLIT_ON", 'audio.tts.split_on': 'AUDIO_TTS_SPLIT_ON',
"audio.tts.voice": "AUDIO_TTS_VOICE", 'audio.tts.voice': 'AUDIO_TTS_VOICE',
"auth.admin.email": "ADMIN_EMAIL", 'auth.admin.email': 'ADMIN_EMAIL',
"auth.admin.show": "SHOW_ADMIN_DETAILS", 'auth.admin.show': 'SHOW_ADMIN_DETAILS',
"auth.api_key.allowed_endpoints": "API_KEYS_ALLOWED_ENDPOINTS", 'auth.api_key.allowed_endpoints': 'API_KEYS_ALLOWED_ENDPOINTS',
"auth.api_key.endpoint_restrictions": "ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS", 'auth.api_key.endpoint_restrictions': 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS',
"auth.enable_api_keys": "ENABLE_API_KEYS", 'auth.enable_api_keys': 'ENABLE_API_KEYS',
"auth.jwt_expiry": "JWT_EXPIRES_IN", 'auth.jwt_expiry': 'JWT_EXPIRES_IN',
"automations.enable": "ENABLE_AUTOMATIONS", 'automations.enable': 'ENABLE_AUTOMATIONS',
"automations.max_count": "AUTOMATION_MAX_COUNT", 'automations.max_count': 'AUTOMATION_MAX_COUNT',
"automations.min_interval": "AUTOMATION_MIN_INTERVAL", 'automations.min_interval': 'AUTOMATION_MIN_INTERVAL',
"calendar.enable": "ENABLE_CALENDAR", 'calendar.enable': 'ENABLE_CALENDAR',
"channels.enable": "ENABLE_CHANNELS", 'channels.enable': 'ENABLE_CHANNELS',
"code_execution.enable": "ENABLE_CODE_EXECUTION", 'code_execution.enable': 'ENABLE_CODE_EXECUTION',
"code_execution.engine": "CODE_EXECUTION_ENGINE", 'code_execution.engine': 'CODE_EXECUTION_ENGINE',
"code_execution.jupyter.auth": "CODE_EXECUTION_JUPYTER_AUTH", 'code_execution.jupyter.auth': 'CODE_EXECUTION_JUPYTER_AUTH',
"code_execution.jupyter.auth_password": "CODE_EXECUTION_JUPYTER_AUTH_PASSWORD", 'code_execution.jupyter.auth_password': 'CODE_EXECUTION_JUPYTER_AUTH_PASSWORD',
"code_execution.jupyter.auth_token": "CODE_EXECUTION_JUPYTER_AUTH_TOKEN", 'code_execution.jupyter.auth_token': 'CODE_EXECUTION_JUPYTER_AUTH_TOKEN',
"code_execution.jupyter.timeout": "CODE_EXECUTION_JUPYTER_TIMEOUT", 'code_execution.jupyter.timeout': 'CODE_EXECUTION_JUPYTER_TIMEOUT',
"code_execution.jupyter.url": "CODE_EXECUTION_JUPYTER_URL", 'code_execution.jupyter.url': 'CODE_EXECUTION_JUPYTER_URL',
"code_interpreter.enable": "ENABLE_CODE_INTERPRETER", 'code_interpreter.enable': 'ENABLE_CODE_INTERPRETER',
"code_interpreter.engine": "CODE_INTERPRETER_ENGINE", 'code_interpreter.engine': 'CODE_INTERPRETER_ENGINE',
"code_interpreter.jupyter.auth": "CODE_INTERPRETER_JUPYTER_AUTH", 'code_interpreter.jupyter.auth': 'CODE_INTERPRETER_JUPYTER_AUTH',
"code_interpreter.jupyter.auth_password": "CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD", 'code_interpreter.jupyter.auth_password': 'CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD',
"code_interpreter.jupyter.auth_token": "CODE_INTERPRETER_JUPYTER_AUTH_TOKEN", 'code_interpreter.jupyter.auth_token': 'CODE_INTERPRETER_JUPYTER_AUTH_TOKEN',
"code_interpreter.jupyter.timeout": "CODE_INTERPRETER_JUPYTER_TIMEOUT", 'code_interpreter.jupyter.timeout': 'CODE_INTERPRETER_JUPYTER_TIMEOUT',
"code_interpreter.jupyter.url": "CODE_INTERPRETER_JUPYTER_URL", 'code_interpreter.jupyter.url': 'CODE_INTERPRETER_JUPYTER_URL',
"code_interpreter.prompt_template": "CODE_INTERPRETER_PROMPT_TEMPLATE", 'code_interpreter.prompt_template': 'CODE_INTERPRETER_PROMPT_TEMPLATE',
"direct.enable": "ENABLE_DIRECT_CONNECTIONS", 'direct.enable': 'ENABLE_DIRECT_CONNECTIONS',
"evaluation.arena.enable": "ENABLE_EVALUATION_ARENA_MODELS", 'evaluation.arena.enable': 'ENABLE_EVALUATION_ARENA_MODELS',
"evaluation.arena.models": "EVALUATION_ARENA_MODELS", 'evaluation.arena.models': 'EVALUATION_ARENA_MODELS',
"file.image_compression_height": "FILE_IMAGE_COMPRESSION_HEIGHT", 'file.image_compression_height': 'FILE_IMAGE_COMPRESSION_HEIGHT',
"file.image_compression_width": "FILE_IMAGE_COMPRESSION_WIDTH", 'file.image_compression_width': 'FILE_IMAGE_COMPRESSION_WIDTH',
"folders.enable": "ENABLE_FOLDERS", 'folders.enable': 'ENABLE_FOLDERS',
"folders.max_file_count": "FOLDER_MAX_FILE_COUNT", 'folders.max_file_count': 'FOLDER_MAX_FILE_COUNT',
"google_drive.api_key": "GOOGLE_DRIVE_API_KEY", 'google_drive.api_key': 'GOOGLE_DRIVE_API_KEY',
"google_drive.client_id": "GOOGLE_DRIVE_CLIENT_ID", 'google_drive.client_id': 'GOOGLE_DRIVE_CLIENT_ID',
"google_drive.enable": "ENABLE_GOOGLE_DRIVE_INTEGRATION", 'google_drive.enable': 'ENABLE_GOOGLE_DRIVE_INTEGRATION',
"image_generation.automatic1111.api_auth": "AUTOMATIC1111_API_AUTH", 'image_generation.automatic1111.api_auth': 'AUTOMATIC1111_API_AUTH',
"image_generation.automatic1111.api_params": "AUTOMATIC1111_PARAMS", 'image_generation.automatic1111.api_params': 'AUTOMATIC1111_PARAMS',
"image_generation.automatic1111.base_url": "AUTOMATIC1111_BASE_URL", 'image_generation.automatic1111.base_url': 'AUTOMATIC1111_BASE_URL',
"image_generation.comfyui.api_key": "COMFYUI_API_KEY", 'image_generation.comfyui.api_key': 'COMFYUI_API_KEY',
"image_generation.comfyui.base_url": "COMFYUI_BASE_URL", 'image_generation.comfyui.base_url': 'COMFYUI_BASE_URL',
"image_generation.comfyui.nodes": "COMFYUI_WORKFLOW_NODES", 'image_generation.comfyui.nodes': 'COMFYUI_WORKFLOW_NODES',
"image_generation.comfyui.workflow": "COMFYUI_WORKFLOW", 'image_generation.comfyui.workflow': 'COMFYUI_WORKFLOW',
"image_generation.enable": "ENABLE_IMAGE_GENERATION", 'image_generation.enable': 'ENABLE_IMAGE_GENERATION',
"image_generation.engine": "IMAGE_GENERATION_ENGINE", 'image_generation.engine': 'IMAGE_GENERATION_ENGINE',
"image_generation.gemini.api_base_url": "IMAGES_GEMINI_API_BASE_URL", 'image_generation.gemini.api_base_url': 'IMAGES_GEMINI_API_BASE_URL',
"image_generation.gemini.api_key": "IMAGES_GEMINI_API_KEY", 'image_generation.gemini.api_key': 'IMAGES_GEMINI_API_KEY',
"image_generation.gemini.endpoint_method": "IMAGES_GEMINI_ENDPOINT_METHOD", 'image_generation.gemini.endpoint_method': 'IMAGES_GEMINI_ENDPOINT_METHOD',
"image_generation.model": "IMAGE_GENERATION_MODEL", 'image_generation.model': 'IMAGE_GENERATION_MODEL',
"image_generation.openai.api_base_url": "IMAGES_OPENAI_API_BASE_URL", 'image_generation.openai.api_base_url': 'IMAGES_OPENAI_API_BASE_URL',
"image_generation.openai.api_key": "IMAGES_OPENAI_API_KEY", 'image_generation.openai.api_key': 'IMAGES_OPENAI_API_KEY',
"image_generation.openai.api_version": "IMAGES_OPENAI_API_VERSION", 'image_generation.openai.api_version': 'IMAGES_OPENAI_API_VERSION',
"image_generation.openai.params": "IMAGES_OPENAI_API_PARAMS", 'image_generation.openai.params': 'IMAGES_OPENAI_API_PARAMS',
"image_generation.prompt.enable": "ENABLE_IMAGE_PROMPT_GENERATION", 'image_generation.prompt.enable': 'ENABLE_IMAGE_PROMPT_GENERATION',
"image_generation.size": "IMAGE_SIZE", 'image_generation.size': 'IMAGE_SIZE',
"image_generation.steps": "IMAGE_STEPS", 'image_generation.steps': 'IMAGE_STEPS',
"images.edit.comfyui.api_key": "IMAGES_EDIT_COMFYUI_API_KEY", 'images.edit.comfyui.api_key': 'IMAGES_EDIT_COMFYUI_API_KEY',
"images.edit.comfyui.base_url": "IMAGES_EDIT_COMFYUI_BASE_URL", 'images.edit.comfyui.base_url': 'IMAGES_EDIT_COMFYUI_BASE_URL',
"images.edit.comfyui.nodes": "IMAGES_EDIT_COMFYUI_WORKFLOW_NODES", 'images.edit.comfyui.nodes': 'IMAGES_EDIT_COMFYUI_WORKFLOW_NODES',
"images.edit.comfyui.workflow": "IMAGES_EDIT_COMFYUI_WORKFLOW", 'images.edit.comfyui.workflow': 'IMAGES_EDIT_COMFYUI_WORKFLOW',
"images.edit.enable": "ENABLE_IMAGE_EDIT", 'images.edit.enable': 'ENABLE_IMAGE_EDIT',
"images.edit.engine": "IMAGE_EDIT_ENGINE", 'images.edit.engine': 'IMAGE_EDIT_ENGINE',
"images.edit.gemini.api_base_url": "IMAGES_EDIT_GEMINI_API_BASE_URL", 'images.edit.gemini.api_base_url': 'IMAGES_EDIT_GEMINI_API_BASE_URL',
"images.edit.gemini.api_key": "IMAGES_EDIT_GEMINI_API_KEY", 'images.edit.gemini.api_key': 'IMAGES_EDIT_GEMINI_API_KEY',
"images.edit.model": "IMAGE_EDIT_MODEL", 'images.edit.model': 'IMAGE_EDIT_MODEL',
"images.edit.openai.api_base_url": "IMAGES_EDIT_OPENAI_API_BASE_URL", '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_key': 'IMAGES_EDIT_OPENAI_API_KEY',
"images.edit.openai.api_version": "IMAGES_EDIT_OPENAI_API_VERSION", 'images.edit.openai.api_version': 'IMAGES_EDIT_OPENAI_API_VERSION',
"images.edit.size": "IMAGE_EDIT_SIZE", 'images.edit.size': 'IMAGE_EDIT_SIZE',
"ldap.enable": "ENABLE_LDAP", 'ldap.enable': 'ENABLE_LDAP',
"ldap.group.enable_creation": "ENABLE_LDAP_GROUP_CREATION", 'ldap.group.enable_creation': 'ENABLE_LDAP_GROUP_CREATION',
"ldap.group.enable_management": "ENABLE_LDAP_GROUP_MANAGEMENT", 'ldap.group.enable_management': 'ENABLE_LDAP_GROUP_MANAGEMENT',
"ldap.server.app_dn": "LDAP_APP_DN", 'ldap.server.app_dn': 'LDAP_APP_DN',
"ldap.server.app_password": "LDAP_APP_PASSWORD", 'ldap.server.app_password': 'LDAP_APP_PASSWORD',
"ldap.server.attribute_for_groups": "LDAP_ATTRIBUTE_FOR_GROUPS", 'ldap.server.attribute_for_groups': 'LDAP_ATTRIBUTE_FOR_GROUPS',
"ldap.server.attribute_for_mail": "LDAP_ATTRIBUTE_FOR_MAIL", 'ldap.server.attribute_for_mail': 'LDAP_ATTRIBUTE_FOR_MAIL',
"ldap.server.attribute_for_username": "LDAP_ATTRIBUTE_FOR_USERNAME", 'ldap.server.attribute_for_username': 'LDAP_ATTRIBUTE_FOR_USERNAME',
"ldap.server.ca_cert_file": "LDAP_CA_CERT_FILE", 'ldap.server.ca_cert_file': 'LDAP_CA_CERT_FILE',
"ldap.server.ciphers": "LDAP_CIPHERS", 'ldap.server.ciphers': 'LDAP_CIPHERS',
"ldap.server.host": "LDAP_SERVER_HOST", 'ldap.server.host': 'LDAP_SERVER_HOST',
"ldap.server.label": "LDAP_SERVER_LABEL", 'ldap.server.label': 'LDAP_SERVER_LABEL',
"ldap.server.port": "LDAP_SERVER_PORT", 'ldap.server.port': 'LDAP_SERVER_PORT',
"ldap.server.search_filter": "LDAP_SEARCH_FILTER", 'ldap.server.search_filter': 'LDAP_SEARCH_FILTER',
"ldap.server.use_tls": "LDAP_USE_TLS", 'ldap.server.use_tls': 'LDAP_USE_TLS',
"ldap.server.users_dn": "LDAP_SEARCH_BASE", 'ldap.server.users_dn': 'LDAP_SEARCH_BASE',
"ldap.server.validate_cert": "LDAP_VALIDATE_CERT", 'ldap.server.validate_cert': 'LDAP_VALIDATE_CERT',
"memories.enable": "ENABLE_MEMORIES", 'memories.enable': 'ENABLE_MEMORIES',
"models.base_models_cache": "ENABLE_BASE_MODELS_CACHE", 'models.base_models_cache': 'ENABLE_BASE_MODELS_CACHE',
"models.default_metadata": "DEFAULT_MODEL_METADATA", 'models.default_metadata': 'DEFAULT_MODEL_METADATA',
"models.default_params": "DEFAULT_MODEL_PARAMS", 'models.default_params': 'DEFAULT_MODEL_PARAMS',
"notes.enable": "ENABLE_NOTES", 'notes.enable': 'ENABLE_NOTES',
# OAuth — direct paths # OAuth — direct paths
"oauth.admin_roles": "OAUTH_ADMIN_ROLES", 'oauth.admin_roles': 'OAUTH_ADMIN_ROLES',
"oauth.allowed_domains": "OAUTH_ALLOWED_DOMAINS", 'oauth.allowed_domains': 'OAUTH_ALLOWED_DOMAINS',
"oauth.allowed_roles": "OAUTH_ALLOWED_ROLES", 'oauth.allowed_roles': 'OAUTH_ALLOWED_ROLES',
"oauth.audience": "OAUTH_AUDIENCE", 'oauth.audience': 'OAUTH_AUDIENCE',
"oauth.auto_redirect": "OAUTH_AUTO_REDIRECT", 'oauth.auto_redirect': 'OAUTH_AUTO_REDIRECT',
"oauth.blocked_groups": "OAUTH_BLOCKED_GROUPS", 'oauth.blocked_groups': 'OAUTH_BLOCKED_GROUPS',
"oauth.client.timeout": "OAUTH_CLIENT_TIMEOUT", 'oauth.client.timeout': 'OAUTH_CLIENT_TIMEOUT',
"oauth.enable_group_creation": "ENABLE_OAUTH_GROUP_CREATION", 'oauth.enable_group_creation': 'ENABLE_OAUTH_GROUP_CREATION',
"oauth.enable_group_mapping": "ENABLE_OAUTH_GROUP_MANAGEMENT", 'oauth.enable_group_mapping': 'ENABLE_OAUTH_GROUP_MANAGEMENT',
"oauth.enable_role_mapping": "ENABLE_OAUTH_ROLE_MANAGEMENT", 'oauth.enable_role_mapping': 'ENABLE_OAUTH_ROLE_MANAGEMENT',
"oauth.enable_signup": "ENABLE_OAUTH_SIGNUP", 'oauth.enable_signup': 'ENABLE_OAUTH_SIGNUP',
"oauth.group_default_share": "OAUTH_GROUP_DEFAULT_SHARE", 'oauth.group_default_share': 'OAUTH_GROUP_DEFAULT_SHARE',
"oauth.merge_accounts_by_email": "OAUTH_MERGE_ACCOUNTS_BY_EMAIL", 'oauth.merge_accounts_by_email': 'OAUTH_MERGE_ACCOUNTS_BY_EMAIL',
"oauth.refresh_token_include_scope": "OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE", 'oauth.refresh_token_include_scope': 'OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE',
"oauth.roles_claim": "OAUTH_ROLES_CLAIM", 'oauth.roles_claim': 'OAUTH_ROLES_CLAIM',
"oauth.update_email_on_login": "OAUTH_UPDATE_EMAIL_ON_LOGIN", 'oauth.update_email_on_login': 'OAUTH_UPDATE_EMAIL_ON_LOGIN',
"oauth.update_name_on_login": "OAUTH_UPDATE_NAME_ON_LOGIN", 'oauth.update_name_on_login': 'OAUTH_UPDATE_NAME_ON_LOGIN',
"oauth.update_picture_on_login": "OAUTH_UPDATE_PICTURE_ON_LOGIN", 'oauth.update_picture_on_login': 'OAUTH_UPDATE_PICTURE_ON_LOGIN',
# OAuth — generic provider paths # OAuth — generic provider paths
"oauth.client_id": "OAUTH_CLIENT_ID", 'oauth.client_id': 'OAUTH_CLIENT_ID',
"oauth.client_secret": "OAUTH_CLIENT_SECRET", 'oauth.client_secret': 'OAUTH_CLIENT_SECRET',
"oauth.code_challenge_method": "OAUTH_CODE_CHALLENGE_METHOD", 'oauth.code_challenge_method': 'OAUTH_CODE_CHALLENGE_METHOD',
"oauth.email_claim": "OAUTH_EMAIL_CLAIM", 'oauth.email_claim': 'OAUTH_EMAIL_CLAIM',
"oauth.end_session_endpoint": "OPENID_END_SESSION_ENDPOINT", 'oauth.end_session_endpoint': 'OPENID_END_SESSION_ENDPOINT',
"oauth.group_claim": "OAUTH_GROUP_CLAIM", 'oauth.group_claim': 'OAUTH_GROUP_CLAIM',
"oauth.picture_claim": "OAUTH_PICTURE_CLAIM", 'oauth.picture_claim': 'OAUTH_PICTURE_CLAIM',
"oauth.provider_name": "OAUTH_PROVIDER_NAME", 'oauth.provider_name': 'OAUTH_PROVIDER_NAME',
"oauth.provider_url": "OPENID_PROVIDER_URL", 'oauth.provider_url': 'OPENID_PROVIDER_URL',
"oauth.redirect_uri": "OPENID_REDIRECT_URI", 'oauth.redirect_uri': 'OPENID_REDIRECT_URI',
"oauth.scopes": "OAUTH_SCOPES", 'oauth.scopes': 'OAUTH_SCOPES',
"oauth.sub_claim": "OAUTH_SUB_CLAIM", 'oauth.sub_claim': 'OAUTH_SUB_CLAIM',
"oauth.timeout": "OAUTH_TIMEOUT", 'oauth.timeout': 'OAUTH_TIMEOUT',
"oauth.token_endpoint_auth_method": "OAUTH_TOKEN_ENDPOINT_AUTH_METHOD", 'oauth.token_endpoint_auth_method': 'OAUTH_TOKEN_ENDPOINT_AUTH_METHOD',
"oauth.username_claim": "OAUTH_USERNAME_CLAIM", 'oauth.username_claim': 'OAUTH_USERNAME_CLAIM',
# OAuth — OIDC nested paths (flattened) # OAuth — OIDC nested paths (flattened)
"oauth.oidc.avatar_claim": "OAUTH_PICTURE_CLAIM", 'oauth.oidc.avatar_claim': 'OAUTH_PICTURE_CLAIM',
"oauth.oidc.client_id": "OAUTH_CLIENT_ID", 'oauth.oidc.client_id': 'OAUTH_CLIENT_ID',
"oauth.oidc.client_secret": "OAUTH_CLIENT_SECRET", 'oauth.oidc.client_secret': 'OAUTH_CLIENT_SECRET',
"oauth.oidc.code_challenge_method": "OAUTH_CODE_CHALLENGE_METHOD", 'oauth.oidc.code_challenge_method': 'OAUTH_CODE_CHALLENGE_METHOD',
"oauth.oidc.email_claim": "OAUTH_EMAIL_CLAIM", 'oauth.oidc.email_claim': 'OAUTH_EMAIL_CLAIM',
"oauth.oidc.end_session_endpoint": "OPENID_END_SESSION_ENDPOINT", 'oauth.oidc.end_session_endpoint': 'OPENID_END_SESSION_ENDPOINT',
"oauth.oidc.group_claim": "OAUTH_GROUP_CLAIM", # renamed from OAUTH_GROUPS_CLAIM 'oauth.oidc.group_claim': 'OAUTH_GROUP_CLAIM', # renamed from OAUTH_GROUPS_CLAIM
"oauth.oidc.oauth_timeout": "OAUTH_TIMEOUT", 'oauth.oidc.oauth_timeout': 'OAUTH_TIMEOUT',
"oauth.oidc.provider_name": "OAUTH_PROVIDER_NAME", 'oauth.oidc.provider_name': 'OAUTH_PROVIDER_NAME',
"oauth.oidc.provider_url": "OPENID_PROVIDER_URL", 'oauth.oidc.provider_url': 'OPENID_PROVIDER_URL',
"oauth.oidc.redirect_uri": "OPENID_REDIRECT_URI", 'oauth.oidc.redirect_uri': 'OPENID_REDIRECT_URI',
"oauth.oidc.scopes": "OAUTH_SCOPES", 'oauth.oidc.scopes': 'OAUTH_SCOPES',
"oauth.oidc.sub_claim": "OAUTH_SUB_CLAIM", 'oauth.oidc.sub_claim': 'OAUTH_SUB_CLAIM',
"oauth.oidc.token_endpoint_auth_method": "OAUTH_TOKEN_ENDPOINT_AUTH_METHOD", 'oauth.oidc.token_endpoint_auth_method': 'OAUTH_TOKEN_ENDPOINT_AUTH_METHOD',
"oauth.oidc.username_claim": "OAUTH_USERNAME_CLAIM", 'oauth.oidc.username_claim': 'OAUTH_USERNAME_CLAIM',
# OAuth — provider-specific # OAuth — provider-specific
"oauth.feishu.client_id": "FEISHU_CLIENT_ID", 'oauth.feishu.client_id': 'FEISHU_CLIENT_ID',
"oauth.feishu.client_secret": "FEISHU_CLIENT_SECRET", 'oauth.feishu.client_secret': 'FEISHU_CLIENT_SECRET',
"oauth.feishu.redirect_uri": "FEISHU_REDIRECT_URI", 'oauth.feishu.redirect_uri': 'FEISHU_REDIRECT_URI',
"oauth.feishu.scope": "FEISHU_OAUTH_SCOPE", 'oauth.feishu.scope': 'FEISHU_OAUTH_SCOPE',
"oauth.github.client_id": "GITHUB_CLIENT_ID", 'oauth.github.client_id': 'GITHUB_CLIENT_ID',
"oauth.github.client_secret": "GITHUB_CLIENT_SECRET", 'oauth.github.client_secret': 'GITHUB_CLIENT_SECRET',
"oauth.github.redirect_uri": "GITHUB_CLIENT_REDIRECT_URI", 'oauth.github.redirect_uri': 'GITHUB_CLIENT_REDIRECT_URI',
"oauth.github.scope": "GITHUB_CLIENT_SCOPE", 'oauth.github.scope': 'GITHUB_CLIENT_SCOPE',
"oauth.google.client_id": "GOOGLE_CLIENT_ID", 'oauth.google.client_id': 'GOOGLE_CLIENT_ID',
"oauth.google.client_secret": "GOOGLE_CLIENT_SECRET", 'oauth.google.client_secret': 'GOOGLE_CLIENT_SECRET',
"oauth.google.redirect_uri": "GOOGLE_REDIRECT_URI", 'oauth.google.redirect_uri': 'GOOGLE_REDIRECT_URI',
"oauth.google.scope": "GOOGLE_OAUTH_SCOPE", 'oauth.google.scope': 'GOOGLE_OAUTH_SCOPE',
"oauth.microsoft.client_id": "MICROSOFT_CLIENT_ID", 'oauth.microsoft.client_id': 'MICROSOFT_CLIENT_ID',
"oauth.microsoft.client_secret": "MICROSOFT_CLIENT_SECRET", 'oauth.microsoft.client_secret': 'MICROSOFT_CLIENT_SECRET',
"oauth.microsoft.login_base_url": "MICROSOFT_CLIENT_LOGIN_BASE_URL", 'oauth.microsoft.login_base_url': 'MICROSOFT_CLIENT_LOGIN_BASE_URL',
"oauth.microsoft.picture_url": "MICROSOFT_CLIENT_PICTURE_URL", 'oauth.microsoft.picture_url': 'MICROSOFT_CLIENT_PICTURE_URL',
"oauth.microsoft.redirect_uri": "MICROSOFT_REDIRECT_URI", 'oauth.microsoft.redirect_uri': 'MICROSOFT_REDIRECT_URI',
"oauth.microsoft.scope": "MICROSOFT_OAUTH_SCOPE", 'oauth.microsoft.scope': 'MICROSOFT_OAUTH_SCOPE',
"oauth.microsoft.tenant_id": "MICROSOFT_CLIENT_TENANT_ID", 'oauth.microsoft.tenant_id': 'MICROSOFT_CLIENT_TENANT_ID',
# Ollama / OpenAI # Ollama / OpenAI
"ollama.api_configs": "OLLAMA_API_CONFIGS", 'ollama.api_configs': 'OLLAMA_API_CONFIGS',
"ollama.base_urls": "OLLAMA_BASE_URLS", 'ollama.base_urls': 'OLLAMA_BASE_URLS',
"ollama.enable": "ENABLE_OLLAMA_API", 'ollama.enable': 'ENABLE_OLLAMA_API',
"onedrive.enable": "ENABLE_ONEDRIVE_INTEGRATION", 'onedrive.enable': 'ENABLE_ONEDRIVE_INTEGRATION',
"onedrive.sharepoint_tenant_id": "ONEDRIVE_SHAREPOINT_TENANT_ID", 'onedrive.sharepoint_tenant_id': 'ONEDRIVE_SHAREPOINT_TENANT_ID',
"onedrive.sharepoint_url": "ONEDRIVE_SHAREPOINT_URL", 'onedrive.sharepoint_url': 'ONEDRIVE_SHAREPOINT_URL',
"openai.api_base_urls": "OPENAI_API_BASE_URLS", 'openai.api_base_urls': 'OPENAI_API_BASE_URLS',
"openai.api_configs": "OPENAI_API_CONFIGS", 'openai.api_configs': 'OPENAI_API_CONFIGS',
"openai.api_keys": "OPENAI_API_KEYS", 'openai.api_keys': 'OPENAI_API_KEYS',
"openai.enable": "ENABLE_OPENAI_API", 'openai.enable': 'ENABLE_OPENAI_API',
# RAG # RAG
"rag.content_extraction_engine": "CONTENT_EXTRACTION_ENGINE", 'rag.content_extraction_engine': 'CONTENT_EXTRACTION_ENGINE',
"rag.datalab_marker_use_llm": "DATALAB_MARKER_USE_LLM", 'rag.datalab_marker_use_llm': 'DATALAB_MARKER_USE_LLM',
"rag.mistral_ocr_api_base_url": "MISTRAL_OCR_API_BASE_URL", '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_key': 'RAG_AZURE_OPENAI_API_KEY',
"rag.azure_openai.api_version": "RAG_AZURE_OPENAI_API_VERSION", 'rag.azure_openai.api_version': 'RAG_AZURE_OPENAI_API_VERSION',
"rag.azure_openai.base_url": "RAG_AZURE_OPENAI_BASE_URL", 'rag.azure_openai.base_url': 'RAG_AZURE_OPENAI_BASE_URL',
"rag.bypass_embedding_and_retrieval": "BYPASS_EMBEDDING_AND_RETRIEVAL", 'rag.bypass_embedding_and_retrieval': 'BYPASS_EMBEDDING_AND_RETRIEVAL',
"rag.chunk_min_size_target": "CHUNK_MIN_SIZE_TARGET", 'rag.chunk_min_size_target': 'CHUNK_MIN_SIZE_TARGET',
"rag.chunk_overlap": "CHUNK_OVERLAP", 'rag.chunk_overlap': 'CHUNK_OVERLAP',
"rag.chunk_size": "CHUNK_SIZE", 'rag.chunk_size': 'CHUNK_SIZE',
"rag.datalab_marker_additional_config": "DATALAB_MARKER_ADDITIONAL_CONFIG", 'rag.datalab_marker_additional_config': 'DATALAB_MARKER_ADDITIONAL_CONFIG',
"rag.datalab_marker_api_base_url": "DATALAB_MARKER_API_BASE_URL", 'rag.datalab_marker_api_base_url': 'DATALAB_MARKER_API_BASE_URL',
"rag.datalab_marker_api_key": "DATALAB_MARKER_API_KEY", 'rag.datalab_marker_api_key': 'DATALAB_MARKER_API_KEY',
"rag.datalab_marker_disable_image_extraction": "DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION", 'rag.datalab_marker_disable_image_extraction': 'DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION',
"rag.datalab_marker_force_ocr": "DATALAB_MARKER_FORCE_OCR", 'rag.datalab_marker_force_ocr': 'DATALAB_MARKER_FORCE_OCR',
"rag.datalab_marker_format_lines": "DATALAB_MARKER_FORMAT_LINES", 'rag.datalab_marker_format_lines': 'DATALAB_MARKER_FORMAT_LINES',
"rag.datalab_marker_output_format": "DATALAB_MARKER_OUTPUT_FORMAT", 'rag.datalab_marker_output_format': 'DATALAB_MARKER_OUTPUT_FORMAT',
"rag.datalab_marker_paginate": "DATALAB_MARKER_PAGINATE", 'rag.datalab_marker_paginate': 'DATALAB_MARKER_PAGINATE',
"rag.datalab_marker_skip_cache": "DATALAB_MARKER_SKIP_CACHE", 'rag.datalab_marker_skip_cache': 'DATALAB_MARKER_SKIP_CACHE',
"rag.datalab_marker_strip_existing_ocr": "DATALAB_MARKER_STRIP_EXISTING_OCR", 'rag.datalab_marker_strip_existing_ocr': 'DATALAB_MARKER_STRIP_EXISTING_OCR',
"rag.docling_api_key": "DOCLING_API_KEY", 'rag.docling_api_key': 'DOCLING_API_KEY',
"rag.docling_params": "DOCLING_PARAMS", 'rag.docling_params': 'DOCLING_PARAMS',
"rag.docling_server_url": "DOCLING_SERVER_URL", 'rag.docling_server_url': 'DOCLING_SERVER_URL',
"rag.document_intelligence_endpoint": "DOCUMENT_INTELLIGENCE_ENDPOINT", 'rag.document_intelligence_endpoint': 'DOCUMENT_INTELLIGENCE_ENDPOINT',
"rag.document_intelligence_key": "DOCUMENT_INTELLIGENCE_KEY", 'rag.document_intelligence_key': 'DOCUMENT_INTELLIGENCE_KEY',
"rag.document_intelligence_model": "DOCUMENT_INTELLIGENCE_MODEL", 'rag.document_intelligence_model': 'DOCUMENT_INTELLIGENCE_MODEL',
"rag.embedding_batch_size": "RAG_EMBEDDING_BATCH_SIZE", 'rag.embedding_batch_size': 'RAG_EMBEDDING_BATCH_SIZE',
"rag.embedding_concurrent_requests": "RAG_EMBEDDING_CONCURRENT_REQUESTS", 'rag.embedding_concurrent_requests': 'RAG_EMBEDDING_CONCURRENT_REQUESTS',
"rag.embedding_engine": "RAG_EMBEDDING_ENGINE", 'rag.embedding_engine': 'RAG_EMBEDDING_ENGINE',
"rag.embedding_model": "RAG_EMBEDDING_MODEL", 'rag.embedding_model': 'RAG_EMBEDDING_MODEL',
"rag.enable_async_embedding": "ENABLE_ASYNC_EMBEDDING", 'rag.enable_async_embedding': 'ENABLE_ASYNC_EMBEDDING',
"rag.enable_hybrid_search": "ENABLE_RAG_HYBRID_SEARCH", 'rag.enable_hybrid_search': 'ENABLE_RAG_HYBRID_SEARCH',
"rag.enable_hybrid_search_enriched_texts": "ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS", 'rag.enable_hybrid_search_enriched_texts': 'ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS',
"rag.enable_markdown_header_text_splitter": "ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER", '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_api_key': 'EXTERNAL_DOCUMENT_LOADER_API_KEY',
"rag.external_document_loader_url": "EXTERNAL_DOCUMENT_LOADER_URL", 'rag.external_document_loader_url': 'EXTERNAL_DOCUMENT_LOADER_URL',
"rag.external_reranker_api_key": "RAG_EXTERNAL_RERANKER_API_KEY", 'rag.external_reranker_api_key': 'RAG_EXTERNAL_RERANKER_API_KEY',
"rag.external_reranker_timeout": "RAG_EXTERNAL_RERANKER_TIMEOUT", 'rag.external_reranker_timeout': 'RAG_EXTERNAL_RERANKER_TIMEOUT',
"rag.external_reranker_url": "RAG_EXTERNAL_RERANKER_URL", 'rag.external_reranker_url': 'RAG_EXTERNAL_RERANKER_URL',
"rag.file.allowed_extensions": "RAG_ALLOWED_FILE_EXTENSIONS", 'rag.file.allowed_extensions': 'RAG_ALLOWED_FILE_EXTENSIONS',
"rag.file.max_count": "RAG_FILE_MAX_COUNT", 'rag.file.max_count': 'RAG_FILE_MAX_COUNT',
"rag.file.max_size": "RAG_FILE_MAX_SIZE", 'rag.file.max_size': 'RAG_FILE_MAX_SIZE',
"rag.full_context": "RAG_FULL_CONTEXT", 'rag.full_context': 'RAG_FULL_CONTEXT',
"rag.hybrid_bm25_weight": "RAG_HYBRID_BM25_WEIGHT", 'rag.hybrid_bm25_weight': 'RAG_HYBRID_BM25_WEIGHT',
"rag.mineru_api_key": "MINERU_API_KEY", 'rag.mineru_api_key': 'MINERU_API_KEY',
"rag.mineru_api_mode": "MINERU_API_MODE", 'rag.mineru_api_mode': 'MINERU_API_MODE',
"rag.mineru_api_timeout": "MINERU_API_TIMEOUT", 'rag.mineru_api_timeout': 'MINERU_API_TIMEOUT',
"rag.mineru_api_url": "MINERU_API_URL", 'rag.mineru_api_url': 'MINERU_API_URL',
"rag.mineru_file_extensions": "MINERU_FILE_EXTENSIONS", 'rag.mineru_file_extensions': 'MINERU_FILE_EXTENSIONS',
"rag.mineru_params": "MINERU_PARAMS", 'rag.mineru_params': 'MINERU_PARAMS',
"rag.mistral_ocr_api_key": "MISTRAL_OCR_API_KEY", 'rag.mistral_ocr_api_key': 'MISTRAL_OCR_API_KEY',
"rag.ollama.key": "RAG_OLLAMA_API_KEY", 'rag.ollama.key': 'RAG_OLLAMA_API_KEY',
"rag.ollama.url": "RAG_OLLAMA_BASE_URL", 'rag.ollama.url': 'RAG_OLLAMA_BASE_URL',
"rag.openai_api_base_url": "RAG_OPENAI_API_BASE_URL", 'rag.openai_api_base_url': 'RAG_OPENAI_API_BASE_URL',
"rag.openai_api_key": "RAG_OPENAI_API_KEY", 'rag.openai_api_key': 'RAG_OPENAI_API_KEY',
"rag.paddleocr_vl_base_url": "PADDLEOCR_VL_BASE_URL", 'rag.paddleocr_vl_base_url': 'PADDLEOCR_VL_BASE_URL',
"rag.paddleocr_vl_token": "PADDLEOCR_VL_TOKEN", 'rag.paddleocr_vl_token': 'PADDLEOCR_VL_TOKEN',
"rag.pdf_extract_images": "PDF_EXTRACT_IMAGES", 'rag.pdf_extract_images': 'PDF_EXTRACT_IMAGES',
"rag.pdf_loader_mode": "PDF_LOADER_MODE", 'rag.pdf_loader_mode': 'PDF_LOADER_MODE',
"rag.relevance_threshold": "RAG_RELEVANCE_THRESHOLD", 'rag.relevance_threshold': 'RAG_RELEVANCE_THRESHOLD',
"rag.reranking_batch_size": "RAG_RERANKING_BATCH_SIZE", 'rag.reranking_batch_size': 'RAG_RERANKING_BATCH_SIZE',
"rag.reranking_engine": "RAG_RERANKING_ENGINE", 'rag.reranking_engine': 'RAG_RERANKING_ENGINE',
"rag.reranking_model": "RAG_RERANKING_MODEL", 'rag.reranking_model': 'RAG_RERANKING_MODEL',
"rag.template": "RAG_TEMPLATE", 'rag.template': 'RAG_TEMPLATE',
"rag.text_splitter": "RAG_TEXT_SPLITTER", 'rag.text_splitter': 'RAG_TEXT_SPLITTER',
"rag.tika_server_url": "TIKA_SERVER_URL", 'rag.tika_server_url': 'TIKA_SERVER_URL',
"rag.tiktoken_encoding_name": "TIKTOKEN_ENCODING_NAME", 'rag.tiktoken_encoding_name': 'TIKTOKEN_ENCODING_NAME',
"rag.top_k": "RAG_TOP_K", 'rag.top_k': 'RAG_TOP_K',
"rag.top_k_reranker": "RAG_TOP_K_RERANKER", 'rag.top_k_reranker': 'RAG_TOP_K_RERANKER',
# RAG — Web # RAG — Web
"rag.web.fetch.max_content_length": "WEB_FETCH_MAX_CONTENT_LENGTH", 'rag.web.fetch.max_content_length': 'WEB_FETCH_MAX_CONTENT_LENGTH',
"rag.web.loader.concurrent_requests": "WEB_LOADER_CONCURRENT_REQUESTS", 'rag.web.loader.concurrent_requests': 'WEB_LOADER_CONCURRENT_REQUESTS',
"rag.web.loader.engine": "WEB_LOADER_ENGINE", '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_api_key': 'EXTERNAL_WEB_LOADER_API_KEY',
"rag.web.loader.external_web_loader_url": "EXTERNAL_WEB_LOADER_URL", '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_key': 'FIRECRAWL_API_KEY',
"rag.web.loader.firecrawl_api_url": "FIRECRAWL_API_BASE_URL", 'rag.web.loader.firecrawl_api_url': 'FIRECRAWL_API_BASE_URL',
"rag.web.loader.firecrawl_timeout": "FIRECRAWL_TIMEOUT", 'rag.web.loader.firecrawl_timeout': 'FIRECRAWL_TIMEOUT',
"rag.web.loader.playwright_timeout": "PLAYWRIGHT_TIMEOUT", 'rag.web.loader.playwright_timeout': 'PLAYWRIGHT_TIMEOUT',
"rag.web.loader.playwright_ws_url": "PLAYWRIGHT_WS_URL", 'rag.web.loader.playwright_ws_url': 'PLAYWRIGHT_WS_URL',
"rag.web.loader.ssl_verification": "ENABLE_WEB_LOADER_SSL_VERIFICATION", 'rag.web.loader.ssl_verification': 'ENABLE_WEB_LOADER_SSL_VERIFICATION',
"rag.web.loader.timeout": "WEB_LOADER_TIMEOUT", '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_api_key': 'AZURE_AI_SEARCH_API_KEY',
"rag.web.search.azure_ai_search_endpoint": "AZURE_AI_SEARCH_ENDPOINT", '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.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_endpoint': 'BING_SEARCH_V7_ENDPOINT',
"rag.web.search.bing_search_v7_subscription_key": "BING_SEARCH_V7_SUBSCRIPTION_KEY", '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.bocha_search_api_key': 'BOCHA_SEARCH_API_KEY',
"rag.web.search.brave_search_api_key": "BRAVE_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.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_embedding_and_retrieval': 'BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL',
"rag.web.search.bypass_web_loader": "BYPASS_WEB_SEARCH_WEB_LOADER", 'rag.web.search.bypass_web_loader': 'BYPASS_WEB_SEARCH_WEB_LOADER',
"rag.web.search.concurrent_requests": "WEB_SEARCH_CONCURRENT_REQUESTS", 'rag.web.search.concurrent_requests': 'WEB_SEARCH_CONCURRENT_REQUESTS',
"rag.web.search.ddgs_backend": "DDGS_BACKEND", 'rag.web.search.ddgs_backend': 'DDGS_BACKEND',
"rag.web.search.domain.filter_list": "WEB_SEARCH_DOMAIN_FILTER_LIST", 'rag.web.search.domain.filter_list': 'WEB_SEARCH_DOMAIN_FILTER_LIST',
"rag.web.search.enable": "ENABLE_WEB_SEARCH", 'rag.web.search.enable': 'ENABLE_WEB_SEARCH',
"rag.web.search.engine": "WEB_SEARCH_ENGINE", 'rag.web.search.engine': 'WEB_SEARCH_ENGINE',
"rag.web.search.exa_api_key": "EXA_API_KEY", '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_api_key': 'EXTERNAL_WEB_SEARCH_API_KEY',
"rag.web.search.external_web_search_url": "EXTERNAL_WEB_SEARCH_URL", '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_api_key': 'GOOGLE_PSE_API_KEY',
"rag.web.search.google_pse_engine_id": "GOOGLE_PSE_ENGINE_ID", '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_base_url': 'JINA_API_BASE_URL',
"rag.web.search.jina_api_key": "JINA_API_KEY", 'rag.web.search.jina_api_key': 'JINA_API_KEY',
"rag.web.search.kagi_search_api_key": "KAGI_SEARCH_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_api_key': 'LINKUP_API_KEY',
"rag.web.search.linkup_search_params": "LINKUP_SEARCH_PARAMS", 'rag.web.search.linkup_search_params': 'LINKUP_SEARCH_PARAMS',
"rag.web.search.mojeek_search_api_key": "MOJEEK_SEARCH_API_KEY", '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.ollama_cloud_api_key': 'OLLAMA_CLOUD_WEB_SEARCH_API_KEY',
"rag.web.search.perplexity_api_key": "PERPLEXITY_API_KEY", 'rag.web.search.perplexity_api_key': 'PERPLEXITY_API_KEY',
"rag.web.search.perplexity_model": "PERPLEXITY_MODEL", 'rag.web.search.perplexity_model': 'PERPLEXITY_MODEL',
"rag.web.search.perplexity_search_api_url": "PERPLEXITY_SEARCH_API_URL", '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.perplexity_search_context_usage': 'PERPLEXITY_SEARCH_CONTEXT_USAGE',
"rag.web.search.result_count": "WEB_SEARCH_RESULT_COUNT", 'rag.web.search.result_count': 'WEB_SEARCH_RESULT_COUNT',
"rag.web.search.searchapi_api_key": "SEARCHAPI_API_KEY", 'rag.web.search.searchapi_api_key': 'SEARCHAPI_API_KEY',
"rag.web.search.searchapi_engine": "SEARCHAPI_ENGINE", 'rag.web.search.searchapi_engine': 'SEARCHAPI_ENGINE',
"rag.web.search.searxng_language": "SEARXNG_LANGUAGE", 'rag.web.search.searxng_language': 'SEARXNG_LANGUAGE',
"rag.web.search.searxng_query_url": "SEARXNG_QUERY_URL", 'rag.web.search.searxng_query_url': 'SEARXNG_QUERY_URL',
"rag.web.search.serpapi_api_key": "SERPAPI_API_KEY", 'rag.web.search.serpapi_api_key': 'SERPAPI_API_KEY',
"rag.web.search.serpapi_engine": "SERPAPI_ENGINE", 'rag.web.search.serpapi_engine': 'SERPAPI_ENGINE',
"rag.web.search.serper_api_key": "SERPER_API_KEY", 'rag.web.search.serper_api_key': 'SERPER_API_KEY',
"rag.web.search.serply_api_key": "SERPLY_API_KEY", 'rag.web.search.serply_api_key': 'SERPLY_API_KEY',
"rag.web.search.serpstack_api_key": "SERPSTACK_API_KEY", 'rag.web.search.serpstack_api_key': 'SERPSTACK_API_KEY',
"rag.web.search.serpstack_https": "SERPSTACK_HTTPS", 'rag.web.search.serpstack_https': 'SERPSTACK_HTTPS',
"rag.web.search.sougou_api_sid": "SOUGOU_API_SID", 'rag.web.search.sougou_api_sid': 'SOUGOU_API_SID',
"rag.web.search.sougou_api_sk": "SOUGOU_API_SK", 'rag.web.search.sougou_api_sk': 'SOUGOU_API_SK',
"rag.web.search.tavily_api_key": "TAVILY_API_KEY", 'rag.web.search.tavily_api_key': 'TAVILY_API_KEY',
"rag.web.search.tavily_extract_depth": "TAVILY_EXTRACT_DEPTH", 'rag.web.search.tavily_extract_depth': 'TAVILY_EXTRACT_DEPTH',
"rag.web.search.trust_env": "WEB_SEARCH_TRUST_ENV", 'rag.web.search.trust_env': 'WEB_SEARCH_TRUST_ENV',
"rag.web.search.yacy_password": "YACY_PASSWORD", 'rag.web.search.yacy_password': 'YACY_PASSWORD',
"rag.web.search.yacy_query_url": "YACY_QUERY_URL", 'rag.web.search.yacy_query_url': 'YACY_QUERY_URL',
"rag.web.search.yacy_username": "YACY_USERNAME", '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_api_key': 'YANDEX_WEB_SEARCH_API_KEY',
"rag.web.search.yandex_web_search_config": "YANDEX_WEB_SEARCH_CONFIG", '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.yandex_web_search_url': 'YANDEX_WEB_SEARCH_URL',
"rag.web.search.youcom_api_key": "YOUCOM_API_KEY", 'rag.web.search.youcom_api_key': 'YOUCOM_API_KEY',
"rag.youtube_loader_language": "YOUTUBE_LOADER_LANGUAGE", 'rag.youtube_loader_language': 'YOUTUBE_LOADER_LANGUAGE',
"rag.youtube_loader_proxy_url": "YOUTUBE_LOADER_PROXY_URL", 'rag.youtube_loader_proxy_url': 'YOUTUBE_LOADER_PROXY_URL',
# Tasks # Tasks
"task.autocomplete.enable": "ENABLE_AUTOCOMPLETE_GENERATION", 'task.autocomplete.enable': 'ENABLE_AUTOCOMPLETE_GENERATION',
"task.autocomplete.input_max_length": "AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH", 'task.autocomplete.input_max_length': 'AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH',
"task.autocomplete.prompt_template": "AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE", 'task.autocomplete.prompt_template': 'AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE',
"task.follow_up.enable": "ENABLE_FOLLOW_UP_GENERATION", 'task.follow_up.enable': 'ENABLE_FOLLOW_UP_GENERATION',
"task.follow_up.prompt_template": "FOLLOW_UP_GENERATION_PROMPT_TEMPLATE", 'task.follow_up.prompt_template': 'FOLLOW_UP_GENERATION_PROMPT_TEMPLATE',
"task.image.prompt_template": "IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE", 'task.image.prompt_template': 'IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE',
"task.model.default": "TASK_MODEL", 'task.model.default': 'TASK_MODEL',
"task.model.external": "TASK_MODEL_EXTERNAL", 'task.model.external': 'TASK_MODEL_EXTERNAL',
"task.query.prompt_template": "QUERY_GENERATION_PROMPT_TEMPLATE", 'task.query.prompt_template': 'QUERY_GENERATION_PROMPT_TEMPLATE',
"task.query.retrieval.enable": "ENABLE_RETRIEVAL_QUERY_GENERATION", 'task.query.retrieval.enable': 'ENABLE_RETRIEVAL_QUERY_GENERATION',
"task.query.search.enable": "ENABLE_SEARCH_QUERY_GENERATION", 'task.query.search.enable': 'ENABLE_SEARCH_QUERY_GENERATION',
"task.tags.enable": "ENABLE_TAGS_GENERATION", 'task.tags.enable': 'ENABLE_TAGS_GENERATION',
"task.tags.prompt_template": "TAGS_GENERATION_PROMPT_TEMPLATE", 'task.tags.prompt_template': 'TAGS_GENERATION_PROMPT_TEMPLATE',
"task.title.enable": "ENABLE_TITLE_GENERATION", 'task.title.enable': 'ENABLE_TITLE_GENERATION',
"task.title.prompt_template": "TITLE_GENERATION_PROMPT_TEMPLATE", 'task.title.prompt_template': 'TITLE_GENERATION_PROMPT_TEMPLATE',
"task.tools.prompt_template": "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE", 'task.tools.prompt_template': 'TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE',
"task.voice.prompt.enable": "ENABLE_VOICE_MODE_PROMPT", 'task.voice.prompt.enable': 'ENABLE_VOICE_MODE_PROMPT',
"task.voice.prompt_template": "VOICE_MODE_PROMPT_TEMPLATE", 'task.voice.prompt_template': 'VOICE_MODE_PROMPT_TEMPLATE',
# Misc # Misc
"terminal_server.connections": "TERMINAL_SERVER_CONNECTIONS", 'terminal_server.connections': 'TERMINAL_SERVER_CONNECTIONS',
"tool_server.connections": "TOOL_SERVER_CONNECTIONS", 'tool_server.connections': 'TOOL_SERVER_CONNECTIONS',
"ui.banners": "WEBUI_BANNERS", 'ui.banners': 'WEBUI_BANNERS',
"ui.default_group_id": "DEFAULT_GROUP_ID", 'ui.default_group_id': 'DEFAULT_GROUP_ID',
"ui.default_locale": "DEFAULT_LOCALE", 'ui.default_locale': 'DEFAULT_LOCALE',
"ui.default_models": "DEFAULT_MODELS", 'ui.default_models': 'DEFAULT_MODELS',
"ui.default_pinned_models": "DEFAULT_PINNED_MODELS", 'ui.default_pinned_models': 'DEFAULT_PINNED_MODELS',
"ui.default_user_role": "DEFAULT_USER_ROLE", 'ui.default_user_role': 'DEFAULT_USER_ROLE',
"ui.enable_community_sharing": "ENABLE_COMMUNITY_SHARING", 'ui.enable_community_sharing': 'ENABLE_COMMUNITY_SHARING',
"ui.enable_login_form": "ENABLE_LOGIN_FORM", 'ui.enable_login_form': 'ENABLE_LOGIN_FORM',
"ui.enable_message_rating": "ENABLE_MESSAGE_RATING", 'ui.enable_message_rating': 'ENABLE_MESSAGE_RATING',
"ui.enable_password_change_form": "ENABLE_PASSWORD_CHANGE_FORM", 'ui.enable_password_change_form': 'ENABLE_PASSWORD_CHANGE_FORM',
"ui.enable_signup": "ENABLE_SIGNUP", 'ui.enable_signup': 'ENABLE_SIGNUP',
"ui.enable_user_webhooks": "ENABLE_USER_WEBHOOKS", 'ui.enable_user_webhooks': 'ENABLE_USER_WEBHOOKS',
"ui.model_order_list": "MODEL_ORDER_LIST", 'ui.model_order_list': 'MODEL_ORDER_LIST',
"ui.pending_user_overlay_content": "PENDING_USER_OVERLAY_CONTENT", 'ui.pending_user_overlay_content': 'PENDING_USER_OVERLAY_CONTENT',
"ui.pending_user_overlay_title": "PENDING_USER_OVERLAY_TITLE", 'ui.pending_user_overlay_title': 'PENDING_USER_OVERLAY_TITLE',
"ui.prompt_suggestions": "DEFAULT_PROMPT_SUGGESTIONS", 'ui.prompt_suggestions': 'DEFAULT_PROMPT_SUGGESTIONS',
"ui.watermark": "RESPONSE_WATERMARK", 'ui.watermark': 'RESPONSE_WATERMARK',
"user.permissions": "USER_PERMISSIONS", 'user.permissions': 'USER_PERMISSIONS',
"users.enable_status": "ENABLE_USER_STATUS", 'users.enable_status': 'ENABLE_USER_STATUS',
"webhook_url": "WEBHOOK_URL", 'webhook_url': 'WEBHOOK_URL',
"webui.url": "WEBUI_URL", 'webui.url': 'WEBUI_URL',
} }
STORAGE_KEY_REWRITES = { STORAGE_KEY_REWRITES = {
"oauth.refresh_token_include_scope": "oauth.refresh_token.include_scope", 'oauth.refresh_token_include_scope': 'oauth.refresh_token.include_scope',
'rag.openai_api_base_url': 'rag.openai.api_base_url',
"rag.openai_api_base_url": "rag.openai.api_base_url", 'rag.openai_api_key': 'rag.openai.api_key',
"rag.openai_api_key": "rag.openai.api_key", 'rag.ollama.url': 'rag.ollama.base_url',
"rag.ollama.url": "rag.ollama.base_url", 'rag.ollama.key': 'rag.ollama.api_key',
"rag.ollama.key": "rag.ollama.api_key", 'oauth.oidc.avatar_claim': 'oauth.picture_claim',
"oauth.oidc.avatar_claim": "oauth.picture_claim", 'oauth.oidc.client_id': 'oauth.client_id',
"oauth.oidc.client_id": "oauth.client_id", 'oauth.oidc.client_secret': 'oauth.client_secret',
"oauth.oidc.client_secret": "oauth.client_secret", 'oauth.oidc.code_challenge_method': 'oauth.code_challenge_method',
"oauth.oidc.code_challenge_method": "oauth.code_challenge_method", 'oauth.oidc.email_claim': 'oauth.email_claim',
"oauth.oidc.email_claim": "oauth.email_claim", 'oauth.oidc.end_session_endpoint': 'oauth.end_session_endpoint',
"oauth.oidc.end_session_endpoint": "oauth.end_session_endpoint", 'oauth.oidc.group_claim': 'oauth.group_claim',
"oauth.oidc.group_claim": "oauth.group_claim", 'oauth.oidc.oauth_timeout': 'oauth.timeout',
"oauth.oidc.oauth_timeout": "oauth.timeout", 'oauth.oidc.provider_name': 'oauth.provider_name',
"oauth.oidc.provider_name": "oauth.provider_name", 'oauth.oidc.provider_url': 'oauth.provider_url',
"oauth.oidc.provider_url": "oauth.provider_url", 'oauth.oidc.redirect_uri': 'oauth.redirect_uri',
"oauth.oidc.redirect_uri": "oauth.redirect_uri", 'oauth.oidc.scopes': 'oauth.scopes',
"oauth.oidc.scopes": "oauth.scopes", 'oauth.oidc.sub_claim': 'oauth.sub_claim',
"oauth.oidc.sub_claim": "oauth.sub_claim", 'oauth.oidc.token_endpoint_auth_method': 'oauth.token_endpoint_auth_method',
"oauth.oidc.token_endpoint_auth_method": "oauth.token_endpoint_auth_method", 'oauth.oidc.username_claim': 'oauth.username_claim',
"oauth.oidc.username_claim": "oauth.username_claim",
} }
LEGACY_KEY_TO_STORAGE_KEY = { LEGACY_KEY_TO_STORAGE_KEY = {
legacy_key: STORAGE_KEY_REWRITES.get(blob_path, blob_path) legacy_key: STORAGE_KEY_REWRITES.get(blob_path, blob_path) for blob_path, legacy_key in BLOB_PATH_TO_KEY.items()
for blob_path, legacy_key in BLOB_PATH_TO_KEY.items()
} }
@@ -466,7 +464,9 @@ def upgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
inspector = sa.inspect(conn) inspector = sa.inspect(conn)
table_names = set(inspector.get_table_names()) 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_old_config = {'id', 'data'}.issubset(config_columns)
has_new_config = {'key', 'value'}.issubset(config_columns) has_new_config = {'key', 'value'}.issubset(config_columns)
@@ -481,9 +481,7 @@ def upgrade() -> None:
blob_data = {} blob_data = {}
if has_old_config: if has_old_config:
try: try:
result = conn.execute( result = conn.execute(sa.select(old_config.c.data).order_by(old_config.c.id.desc()).limit(1))
sa.select(old_config.c.data).order_by(old_config.c.id.desc()).limit(1)
)
row = result.fetchone() row = result.fetchone()
if row and row[0]: if row and row[0]:
raw = row[0] raw = row[0]
@@ -538,10 +536,7 @@ def upgrade() -> None:
now = int(time.time()) now = int(time.time())
op.bulk_insert( op.bulk_insert(
new_config, 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') op.rename_table('config_old', 'config')
return 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) has_per_key_config = {'key', 'value'}.issubset(config_columns)
blob_data = {} blob_data = {}
+1 -5
View File
@@ -222,11 +222,7 @@ class FeedbackTable:
) -> FeedbackListResponse: ) -> FeedbackListResponse:
async with get_async_db_context(db) as db: async with get_async_db_context(db) as db:
stmt = select(Feedback, User).join(User, Feedback.user_id == User.id) stmt = select(Feedback, User).join(User, Feedback.user_id == User.id)
count_stmt = ( count_stmt = select(func.count(Feedback.id)).select_from(Feedback).join(User, Feedback.user_id == User.id)
select(func.count(Feedback.id))
.select_from(Feedback)
.join(User, Feedback.user_id == User.id)
)
if filter: if filter:
# Apply model_id filter (exact match) # Apply model_id filter (exact match)
+3 -7
View File
@@ -144,9 +144,7 @@ class FolderTable:
except Exception: except Exception:
return None return None
async def get_folder_by_id( async def get_folder_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[FolderModel]:
self, id: str, db: Optional[AsyncSession] = None
) -> Optional[FolderModel]:
"""Fetch folder by ID only (no user_id filter). Used for shared access.""" """Fetch folder by ID only (no user_id filter). Used for shared access."""
try: try:
async with get_async_db_context(db) as db: async with get_async_db_context(db) as db:
@@ -159,8 +157,7 @@ class FolderTable:
return None return None
async def get_shared_folder_ids_for_user( async def get_shared_folder_ids_for_user(
self, user_id: str, user_group_ids: set[str], self, user_id: str, user_group_ids: set[str], db: Optional[AsyncSession] = None
db: Optional[AsyncSession] = None
) -> dict[str, str]: ) -> dict[str, str]:
""" """
Returns {folder_id: highest_permission} for all folders shared with user. Returns {folder_id: highest_permission} for all folders shared with user.
@@ -175,8 +172,7 @@ class FolderTable:
] ]
if user_group_ids: if user_group_ids:
conditions.append( conditions.append(
and_(AccessGrant.principal_type == 'group', and_(AccessGrant.principal_type == 'group', AccessGrant.principal_id.in_(user_group_ids))
AccessGrant.principal_id.in_(user_group_ids))
) )
result = await db.execute( result = await db.execute(
select(AccessGrant).filter( select(AccessGrant).filter(
+1 -5
View File
@@ -164,11 +164,7 @@ class FunctionsTable:
func_data['updated_at'] = int(time.time()) func_data['updated_at'] = int(time.time())
if func.id in existing_ids: if func.id in existing_ids:
await db.execute( await db.execute(update(Function).filter_by(id=func.id).values(**func_data))
update(Function)
.filter_by(id=func.id)
.values(**func_data)
)
else: else:
new_func = Function(**func_data) new_func = Function(**func_data)
db.add(new_func) db.add(new_func)
+9 -3
View File
@@ -210,7 +210,9 @@ class MemoriesTable:
) )
db.add(memory) db.add(memory)
await db.flush() 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': elif action == 'replace':
memory_id = operation.get('id') memory_id = operation.get('id')
@@ -228,7 +230,9 @@ class MemoriesTable:
memory.meta = {**(memory.meta or {}), **operation.get('meta')} memory.meta = {**(memory.meta or {}), **operation.get('meta')}
memory.updated_at = now memory.updated_at = now
await db.flush() 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': elif action == 'move':
memory_id = operation.get('id') memory_id = operation.get('id')
@@ -241,7 +245,9 @@ class MemoriesTable:
memory.meta = {**(memory.meta or {}), **operation.get('meta')} memory.meta = {**(memory.meta or {}), **operation.get('meta')}
memory.updated_at = now memory.updated_at = now
await db.flush() 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': elif action == 'remove':
memory_id = operation.get('id') memory_id = operation.get('id')
+1 -4
View File
@@ -540,10 +540,7 @@ class MessageTable:
) )
grouped[mid][reaction.name]['count'] += 1 grouped[mid][reaction.name]['count'] += 1
return { return {mid: [Reactions(**r) for r in reactions.values()] for mid, reactions in grouped.items()}
mid: [Reactions(**r) for r in reactions.values()]
for mid, reactions in grouped.items()
}
async def get_thread_reply_counts_by_message_ids( async def get_thread_reply_counts_by_message_ids(
self, ids: list[str], db: Optional[AsyncSession] = None self, ids: list[str], db: Optional[AsyncSession] = None
+1 -3
View File
@@ -244,9 +244,7 @@ class ToolsTable:
try: try:
async with get_async_db_context(db) as db: async with get_async_db_context(db) as db:
await db.execute( await db.execute(
update(Tool) update(Tool).filter_by(id=id).values(valves=encrypt_valves(valves), updated_at=int(time.time()))
.filter_by(id=id)
.values(valves=encrypt_valves(valves), updated_at=int(time.time()))
) )
await db.commit() await db.commit()
return await self.get_tool_by_id(id, db=db) return await self.get_tool_by_id(id, db=db)
+1 -1
View File
@@ -192,7 +192,7 @@ async def _retrieve_milvus(connection, auth_config, knowledge, query, count, emb
} }
normalized = [] normalized = []
for hit in (response[0] if response else []): for hit in response[0] if response else []:
item = dict(hit) item = dict(hit)
entity = item.get('entity') or {} entity = item.get('entity') or {}
result = { result = {
@@ -280,10 +280,7 @@ class MilvusClient(VectorDBBase):
for item in items: for item in items:
text = item['text'] or '' text = item['text'] or ''
if len(text) > MILVUS_TEXT_MAX_LENGTH: if len(text) > MILVUS_TEXT_MAX_LENGTH:
log.warning( log.warning(f'Milvus: truncating text id={item["id"]} {len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars')
f'Milvus: truncating text id={item["id"]} '
f'{len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars'
)
text = text[:MILVUS_TEXT_MAX_LENGTH] text = text[:MILVUS_TEXT_MAX_LENGTH]
data.append( data.append(
{ {
@@ -299,10 +296,7 @@ class MilvusClient(VectorDBBase):
data=data, data=data,
) )
except MilvusException as e: except MilvusException as e:
log.error( log.error(f'Milvus insert failed for {self.collection_prefix}_{collection_name} ({len(items)} items): {e}')
f'Milvus insert failed for {self.collection_prefix}_{collection_name} '
f'({len(items)} items): {e}'
)
raise raise
def upsert(self, collection_name: str, items: list[VectorItem]): def upsert(self, collection_name: str, items: list[VectorItem]):
@@ -324,10 +318,7 @@ class MilvusClient(VectorDBBase):
for item in items: for item in items:
text = item['text'] or '' text = item['text'] or ''
if len(text) > MILVUS_TEXT_MAX_LENGTH: if len(text) > MILVUS_TEXT_MAX_LENGTH:
log.warning( log.warning(f'Milvus: truncating text id={item["id"]} {len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars')
f'Milvus: truncating text id={item["id"]} '
f'{len(text)}->{MILVUS_TEXT_MAX_LENGTH} chars'
)
text = text[:MILVUS_TEXT_MAX_LENGTH] text = text[:MILVUS_TEXT_MAX_LENGTH]
data.append( data.append(
{ {
@@ -343,10 +334,7 @@ class MilvusClient(VectorDBBase):
data=data, data=data,
) )
except MilvusException as e: except MilvusException as e:
log.error( log.error(f'Milvus upsert failed for {self.collection_prefix}_{collection_name} ({len(items)} items): {e}')
f'Milvus upsert failed for {self.collection_prefix}_{collection_name} '
f'({len(items)} items): {e}'
)
raise raise
def delete( def delete(
+3 -8
View File
@@ -292,8 +292,7 @@ async def update_audio_config(request: Request, form_data: AudioConfigUpdateForm
if form_data.stt.ENGINE == '': if form_data.stt.ENGINE == '':
request.app.state.faster_whisper_model = await asyncio.to_thread( request.app.state.faster_whisper_model = await asyncio.to_thread(
set_faster_whisper_model, set_faster_whisper_model, form_data.stt.WHISPER_MODEL, WHISPER_MODEL_AUTO_UPDATE
form_data.stt.WHISPER_MODEL, WHISPER_MODEL_AUTO_UPDATE
) )
else: else:
request.app.state.faster_whisper_model = None 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, detail=ERROR_MESSAGES.NOT_FOUND,
) )
if user.role != 'admin' and not await has_permission( if user.role != 'admin' and not await has_permission(user.id, 'chat.tts', await Config.get('user.permissions')):
user.id, 'chat.tts', await Config.get('user.permissions')
):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -1152,9 +1149,7 @@ async def transcription(
language: Optional[str] = Form(None), language: Optional[str] = Form(None),
user=Depends(get_verified_user), user=Depends(get_verified_user),
): ):
if user.role != 'admin' and not await has_permission( if user.role != 'admin' and not await has_permission(user.id, 'chat.stt', await Config.get('user.permissions')):
user.id, 'chat.stt', await Config.get('user.permissions')
):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+10 -10
View File
@@ -186,7 +186,8 @@ async def create_session_response(
request, request,
EVENTS.AUTH_LOGIN, EVENTS.AUTH_LOGIN,
actor=user, actor=user,
subject_id=user.id, subject_type='user', subject_id=user.id,
subject_type='user',
source=source, source=source,
data={'auth_method': source}, data={'auth_method': source},
) )
@@ -386,7 +387,8 @@ async def update_password(
request, request,
EVENTS.AUTH_PASSWORD_CHANGED, EVENTS.AUTH_PASSWORD_CHANGED,
actor=user, actor=user,
subject_id=user.id, subject_type='user', subject_id=user.id,
subject_type='user',
) )
return success return success
else: 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): async def _check_api_key_permission(request: Request, user, db: AsyncSession):
if not await Config.get('auth.enable_api_keys') or ( if not await Config.get('auth.enable_api_keys') or (
user.role != 'admin' user.role != 'admin'
and not await has_permission( and not await has_permission(user.id, 'features.api_keys', await Config.get('user.permissions'), db=db)
user.id, 'features.api_keys', await Config.get('user.permissions'), db=db
)
): ):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
@@ -1397,7 +1397,8 @@ async def generate_api_key(
request, request,
EVENTS.AUTH_API_KEY_CREATED, EVENTS.AUTH_API_KEY_CREATED,
actor=user, actor=user,
subject_id=user.id, subject_type='user', subject_id=user.id,
subject_type='user',
) )
return { return {
'api_key': api_key, 'api_key': api_key,
@@ -1418,16 +1419,15 @@ async def delete_api_key(
request, request,
EVENTS.AUTH_API_KEY_DELETED, EVENTS.AUTH_API_KEY_DELETED,
actor=user, actor=user,
subject_id=user.id, subject_type='user', subject_id=user.id,
subject_type='user',
) )
return success return success
# get api key # get api key
@router.get('/api_key', response_model=ApiKey) @router.get('/api_key', response_model=ApiKey)
async def get_api_key( async def get_api_key(request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)
):
await _check_api_key_permission(request, user, db) await _check_api_key_permission(request, user, db)
api_key = await Users.get_user_api_key_by_id(user.id, db=db) api_key = await Users.get_user_api_key_by_id(user.id, db=db)
if api_key: if api_key:
+2 -1
View File
@@ -266,7 +266,8 @@ async def toggle_automation_by_id(
request, request,
EVENTS.AUTOMATION_ENABLED if toggled.is_active else EVENTS.AUTOMATION_DISABLED, EVENTS.AUTOMATION_ENABLED if toggled.is_active else EVENTS.AUTOMATION_DISABLED,
actor=user, actor=user,
subject_id=toggled.id, subject_type='automation', subject_id=toggled.id,
subject_type='automation',
data={'name': toggled.name}, data={'name': toggled.name},
) )
return response return response
+1 -3
View File
@@ -42,9 +42,7 @@ async def check_calendar_permission(request: Request, user):
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.UNAUTHORIZED, detail=ERROR_MESSAGES.UNAUTHORIZED,
) )
if user.role != 'admin' and not await has_permission( if user.role != 'admin' and not await has_permission(user.id, 'features.calendar', config.get('user.permissions')):
user.id, 'features.calendar', config.get('user.permissions')
):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.UNAUTHORIZED, detail=ERROR_MESSAGES.UNAUTHORIZED,
+18 -12
View File
@@ -575,9 +575,7 @@ async def delete_all_user_chats(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session), db: AsyncSession = Depends(get_async_session),
): ):
if user.role == 'user' and not await has_permission( if user.role == 'user' and not await has_permission(user.id, 'chat.delete', await Config.get('user.permissions')):
user.id, 'chat.delete', await Config.get('user.permissions')
):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -589,7 +587,8 @@ async def delete_all_user_chats(
request, request,
EVENTS.CHAT_DELETED_ALL, EVENTS.CHAT_DELETED_ALL,
actor=user, actor=user,
subject_id=user.id, subject_type='user', subject_id=user.id,
subject_type='user',
) )
return result return result
@@ -1001,7 +1000,8 @@ async def unshare_all_chats(
request, request,
EVENTS.CHAT_UNSHARED, EVENTS.CHAT_UNSHARED,
actor=user, 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}, data={'count': len(chat_ids), 'chat_ids': chat_ids},
) )
return result 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) chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat: if chat:
updated_chat = {**chat.chat, **form_data.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 # Re-derive content from output for assistant messages so that frontend
# edits to output items are reflected in content. Only when output # 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. # (e.g. a `replace` event or an outlet filter footer) would be reverted.
existing_messages = (chat.chat.get('history') or {}).get('messages') or {} existing_messages = (chat.chat.get('history') or {}).get('messages') or {}
for msg_id, msg in updated_chat.get('history', {}).get('messages', {}).items(): 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'): if msg.get('output') != existing_messages.get(msg_id, {}).get('output'):
msg['content'] = serialize_output(msg['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. # history with potential edits, deletions, or new branches.
messages = (updated_chat.get('history') or {}).get('messages') or {} messages = (updated_chat.get('history') or {}).get('messages') or {}
if messages: 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( await publish_event(
request, request,
@@ -1470,7 +1476,8 @@ async def pin_chat_by_id(
request, request,
EVENTS.CHAT_PINNED if chat.pinned else EVENTS.CHAT_UNPINNED, EVENTS.CHAT_PINNED if chat.pinned else EVENTS.CHAT_UNPINNED,
actor=user, actor=user,
subject_id=id, subject_type='chat', subject_id=id,
subject_type='chat',
) )
return chat return chat
else: else:
@@ -1643,7 +1650,8 @@ async def archive_chat_by_id(
request, request,
EVENTS.CHAT_ARCHIVED if chat.archived else EVENTS.CHAT_UNARCHIVED, EVENTS.CHAT_ARCHIVED if chat.archived else EVENTS.CHAT_UNARCHIVED,
actor=user, actor=user,
subject_id=id, subject_type='chat', subject_id=id,
subject_type='chat',
) )
return ChatResponse(**chat.model_dump()) return ChatResponse(**chat.model_dump())
else: else:
@@ -1660,9 +1668,7 @@ async def share_chat_by_id(
user=Depends(get_verified_user), user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session), db: AsyncSession = Depends(get_async_session),
): ):
if user.role != 'admin' and not await has_permission( if user.role != 'admin' and not await has_permission(user.id, 'chat.share', await Config.get('user.permissions')):
user.id, 'chat.share', await Config.get('user.permissions')
):
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) 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) chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
+15 -10
View File
@@ -144,7 +144,8 @@ async def set_connections_config(
request, request,
EVENTS.CONFIG_CONNECTIONS_UPDATED, EVENTS.CONFIG_CONNECTIONS_UPDATED,
actor=user, actor=user,
subject_id='connections', subject_type='config', subject_id='connections',
subject_type='config',
data=values, data=values,
) )
return 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: if auth_type in ('oauth_2.1', 'oauth_2.1_static') and server_id:
try: try:
oauth_client_info = resolve_oauth_client_info(connection) oauth_client_info = resolve_oauth_client_info(connection)
oauth_client_info = await recover_static_oauth_client_metadata( oauth_client_info = await recover_static_oauth_client_metadata(connection, oauth_client_info)
connection, oauth_client_info
)
oauth_client_info = apply_connection_oauth_options(connection, oauth_client_info) oauth_client_info = apply_connection_oauth_options(connection, oauth_client_info)
request.app.state.oauth_client_manager.add_client( request.app.state.oauth_client_manager.add_client(
f'{server_type}:{server_id}', f'{server_type}:{server_id}',
@@ -278,7 +277,8 @@ async def set_tool_servers_config(
request, request,
EVENTS.CONFIG_TOOL_SERVERS_UPDATED, EVENTS.CONFIG_TOOL_SERVERS_UPDATED,
actor=user, 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]}, data={'count': len(connections), 'types': [connection.get('type', 'openapi') for connection in connections]},
) )
return {'TOOL_SERVER_CONNECTIONS': connections} return {'TOOL_SERVER_CONNECTIONS': connections}
@@ -330,7 +330,8 @@ async def set_terminal_servers_config(
request, request,
EVENTS.CONFIG_TERMINAL_SERVERS_UPDATED, EVENTS.CONFIG_TERMINAL_SERVERS_UPDATED,
actor=user, actor=user,
subject_id='terminal_server.connections', subject_type='config', subject_id='terminal_server.connections',
subject_type='config',
data={'count': len(connections)}, data={'count': len(connections)},
) )
return {'TERMINAL_SERVER_CONNECTIONS': connections} return {'TERMINAL_SERVER_CONNECTIONS': connections}
@@ -699,7 +700,8 @@ async def set_code_execution_config(
request, request,
EVENTS.CONFIG_CODE_EXECUTION_UPDATED, EVENTS.CONFIG_CODE_EXECUTION_UPDATED,
actor=user, actor=user,
subject_id='code_execution', subject_type='config', subject_id='code_execution',
subject_type='config',
data={ data={
'code_execution_enabled': values.get('ENABLE_CODE_EXECUTION'), 'code_execution_enabled': values.get('ENABLE_CODE_EXECUTION'),
'code_execution_engine': values.get('CODE_EXECUTION_ENGINE'), 'code_execution_engine': values.get('CODE_EXECUTION_ENGINE'),
@@ -741,7 +743,8 @@ async def set_models_config(request: Request, form_data: ModelsConfigForm, user=
request, request,
EVENTS.CONFIG_MODELS_UPDATED, EVENTS.CONFIG_MODELS_UPDATED,
actor=user, actor=user,
subject_id='models', subject_type='config', subject_id='models',
subject_type='config',
data={ data={
'default_models': values.get('DEFAULT_MODELS'), 'default_models': values.get('DEFAULT_MODELS'),
'default_pinned_models': values.get('DEFAULT_PINNED_MODELS'), 'default_pinned_models': values.get('DEFAULT_PINNED_MODELS'),
@@ -773,7 +776,8 @@ async def set_default_suggestions(
request, request,
EVENTS.CONFIG_SUGGESTIONS_UPDATED, EVENTS.CONFIG_SUGGESTIONS_UPDATED,
actor=user, actor=user,
subject_id='ui.prompt_suggestions', subject_type='config', subject_id='ui.prompt_suggestions',
subject_type='config',
data={'count': len(suggestions or [])}, data={'count': len(suggestions or [])},
) )
return suggestions return suggestions
@@ -801,7 +805,8 @@ async def set_banners(
request, request,
EVENTS.CONFIG_BANNERS_UPDATED, EVENTS.CONFIG_BANNERS_UPDATED,
actor=user, actor=user,
subject_id='ui.banners', subject_type='config', subject_id='ui.banners',
subject_type='config',
data={'count': len(banners or [])}, data={'count': len(banners or [])},
) )
return banners return banners
+2 -1
View File
@@ -373,7 +373,8 @@ async def delete_feedbacks(
request, request,
EVENTS.FEEDBACK_DELETED_ALL, EVENTS.FEEDBACK_DELETED_ALL,
actor=user, actor=user,
subject_id=user.id, subject_type='user', subject_id=user.id,
subject_type='user',
) )
return success return success
+4 -4
View File
@@ -270,9 +270,7 @@ async def upload_file(
result_meta = result.meta or {} result_meta = result.meta or {}
result_content_type = ( result_content_type = (
result_meta.get('content_type') result_meta.get('content_type') if isinstance(result_meta, dict) else getattr(result_meta, 'content_type', None)
if isinstance(result_meta, dict)
else getattr(result_meta, 'content_type', None)
) )
await publish_event( await publish_event(
request, request,
@@ -532,7 +530,9 @@ async def count_files(
@router.delete('/all') @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) result = await Files.delete_all_files(db=db)
if result: if result:
try: try:
+22 -22
View File
@@ -182,9 +182,7 @@ async def get_shared_folders(
groups = await Groups.get_groups_by_member_id(user.id, db=db) groups = await Groups.get_groups_by_member_id(user.id, db=db)
group_ids = {g.id for g in groups} group_ids = {g.id for g in groups}
folder_perms = await Folders.get_shared_folder_ids_for_user( folder_perms = await Folders.get_shared_folder_ids_for_user(user.id, group_ids, db=db)
user.id, group_ids, db=db
)
# Filter out folders owned by the user # Filter out folders owned by the user
results = [] results = []
@@ -199,28 +197,30 @@ async def get_shared_folders(
owner = await Users.get_user_by_id(folder.user_id, db=db) owner = await Users.get_user_by_id(folder.user_id, db=db)
owner_cache[folder.user_id] = owner.name if owner else 'Unknown' owner_cache[folder.user_id] = owner.name if owner else 'Unknown'
results.append({ results.append(
**folder.model_dump(), {
'owner_name': owner_cache[folder.user_id], **folder.model_dump(),
'permission': permission, 'owner_name': owner_cache[folder.user_id],
}) 'permission': permission,
}
)
# Also include child folders of shared folders (inheritance) # Also include child folders of shared folders (inheritance)
shared_root_ids = {r['id'] for r in results} shared_root_ids = {r['id'] for r in results}
for root_id in list(shared_root_ids): for root_id in list(shared_root_ids):
root_folder = await Folders.get_folder_by_id(root_id, db=db) root_folder = await Folders.get_folder_by_id(root_id, db=db)
if root_folder: if root_folder:
children = await Folders.get_children_folders_by_id_and_user_id( children = await Folders.get_children_folders_by_id_and_user_id(root_id, root_folder.user_id, db=db)
root_id, root_folder.user_id, db=db
)
if children: if children:
for child in children: for child in children:
if child.id not in {r['id'] for r in results}: if child.id not in {r['id'] for r in results}:
results.append({ results.append(
**child.model_dump(), {
'owner_name': owner_cache.get(child.user_id, 'Unknown'), **child.model_dump(),
'permission': folder_perms.get(root_id, 'read'), 'owner_name': owner_cache.get(child.user_id, 'Unknown'),
}) 'permission': folder_perms.get(root_id, 'read'),
}
)
return results return results
@@ -231,7 +231,9 @@ async def get_shared_folders(
@router.get('/{id}', response_model=None) @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) await check_folders_permission(request, user, db=db)
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db) folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
if folder: if folder:
@@ -443,7 +445,8 @@ async def update_folder_access_by_id(
form_data.access_grants = await filter_allowed_access_grants( form_data.access_grants = await filter_allowed_access_grants(
await Config.get('user.permissions'), await Config.get('user.permissions'),
user.id, user.role, user.id,
user.role,
form_data.access_grants, form_data.access_grants,
None, None,
db=db, db=db,
@@ -509,10 +512,7 @@ async def get_shared_folder_chats(
chat['owner_name'] = owner_cache[uid] chat['owner_name'] = owner_cache[uid]
return { return {
'chats': [ 'chats': [{**chat, 'readonly': chat['user_id'] != user.id} for chat in chats],
{**chat, 'readonly': chat['user_id'] != user.id}
for chat in chats
],
'folder_permission': 'write' if has_write else 'read', 'folder_permission': 'write' if has_write else 'read',
} }
+2 -1
View File
@@ -291,7 +291,8 @@ async def toggle_function_by_id(
request, request,
EVENTS.FUNCTION_ENABLED if function.is_active else EVENTS.FUNCTION_DISABLED, EVENTS.FUNCTION_ENABLED if function.is_active else EVENTS.FUNCTION_DISABLED,
actor=user, actor=user,
subject_id=function.id, subject_type='function', subject_id=function.id,
subject_type='function',
data={'type': function.type, 'name': function.name}, data={'type': function.type, 'name': function.name},
) )
return function return function
+11 -32
View File
@@ -201,25 +201,12 @@ async def set_image_model(request: Request, model: str):
async def get_image_model(request): async def get_image_model(request):
image_config = await get_image_config() image_config = await get_image_config()
if image_config.IMAGE_GENERATION_ENGINE == 'openai': if image_config.IMAGE_GENERATION_ENGINE == 'openai':
return ( return image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else 'dall-e-2'
image_config.IMAGE_GENERATION_MODEL
if image_config.IMAGE_GENERATION_MODEL
else 'dall-e-2'
)
elif image_config.IMAGE_GENERATION_ENGINE == 'gemini': elif image_config.IMAGE_GENERATION_ENGINE == 'gemini':
return ( return image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else 'imagen-3.0-generate-002'
image_config.IMAGE_GENERATION_MODEL
if image_config.IMAGE_GENERATION_MODEL
else 'imagen-3.0-generate-002'
)
elif image_config.IMAGE_GENERATION_ENGINE == 'comfyui': elif image_config.IMAGE_GENERATION_ENGINE == 'comfyui':
return ( return image_config.IMAGE_GENERATION_MODEL if image_config.IMAGE_GENERATION_MODEL else ''
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 == '':
)
elif (
image_config.IMAGE_GENERATION_ENGINE == 'automatic1111'
or image_config.IMAGE_GENERATION_ENGINE == ''
):
try: try:
session = await get_session() session = await get_session()
async with session.get( 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], info['CheckpointLoaderSimple']['input']['required']['ckpt_name'][0],
) )
) )
elif ( elif image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' or image_config.IMAGE_GENERATION_ENGINE == '':
image_config.IMAGE_GENERATION_ENGINE == 'automatic1111'
or image_config.IMAGE_GENERATION_ENGINE == ''
):
session = await get_session() session = await get_session()
async with session.get( async with session.get(
url=f'{image_config.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models', 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, request,
EVENTS.IMAGE_GENERATED, EVENTS.IMAGE_GENERATED,
actor=user, actor=user,
subject_id=None, subject_type='image', subject_id=None,
subject_type='image',
data={ data={
'model': form_data.model, 'model': form_data.model,
'size': form_data.size, 'size': form_data.size,
@@ -658,11 +643,7 @@ async def image_generations(
) )
else {'response_format': 'b64_json'} 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() session = await get_session()
@@ -803,10 +784,7 @@ async def image_generations(
) )
images.append({'url': url}) images.append({'url': url})
return images return images
elif ( elif image_config.IMAGE_GENERATION_ENGINE == 'automatic1111' or image_config.IMAGE_GENERATION_ENGINE == '':
image_config.IMAGE_GENERATION_ENGINE == 'automatic1111'
or image_config.IMAGE_GENERATION_ENGINE == ''
):
if form_data.model: if form_data.model:
await set_image_model(request, 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, request,
EVENTS.IMAGE_EDITED, EVENTS.IMAGE_EDITED,
actor=user, actor=user,
subject_id=None, subject_type='image', subject_id=None,
subject_type='image',
data={ data={
'model': form_data.model, 'model': form_data.model,
'size': form_data.size, 'size': form_data.size,
+4 -4
View File
@@ -570,7 +570,9 @@ async def _set_external_connections(connections: list[dict]) -> None:
await Config.upsert({EXTERNAL_KNOWLEDGE_CONNECTIONS_CONFIG_KEY: connections}) 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) provider, config = _validate_external_connection_form(form_data)
now = int(time.time()) now = int(time.time())
return { return {
@@ -1703,9 +1705,7 @@ async def delete_knowledge_by_id(
connection_id = (knowledge.meta or {}).get('external', {}).get('connection_id') connection_id = (knowledge.meta or {}).get('external', {}).get('connection_id')
if connection_id: if connection_id:
connections = [ connections = [
connection connection for connection in await _get_external_connections() if connection.get('id') != connection_id
for connection in await _get_external_connections()
if connection.get('id') != connection_id
] ]
await _set_external_connections(connections) await _set_external_connections(connections)
else: else:
+5 -5
View File
@@ -39,9 +39,7 @@ async def check_memories_permission(user):
detail=ERROR_MESSAGES.NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND,
) )
if user.role != 'admin' and not await has_permission( if user.role != 'admin' and not await has_permission(user.id, 'features.memories', config.get('user.permissions')):
user.id, 'features.memories', config.get('user.permissions')
):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -431,7 +429,8 @@ async def reset_memory_from_vector_db(
request, request,
EVENTS.MEMORY_RESET, EVENTS.MEMORY_RESET,
actor=user, actor=user,
subject_id=user.id, subject_type='user', subject_id=user.id,
subject_type='user',
data={'count': len(memories)}, data={'count': len(memories)},
) )
return True return True
@@ -461,7 +460,8 @@ async def delete_memory_by_user_id(
request, request,
EVENTS.MEMORY_DELETED, EVENTS.MEMORY_DELETED,
actor=user, actor=user,
subject_id=user.id, subject_type='user', subject_id=user.id,
subject_type='user',
) )
return True return True
+5 -2
View File
@@ -654,7 +654,8 @@ async def toggle_model_by_id(
request, request,
EVENTS.MODEL_ENABLED if model.is_active else EVENTS.MODEL_DISABLED, EVENTS.MODEL_ENABLED if model.is_active else EVENTS.MODEL_DISABLED,
actor=user, actor=user,
subject_id=model.id, subject_type='model', subject_id=model.id,
subject_type='model',
data={'name': model.name}, data={'name': model.name},
) )
return model return model
@@ -867,7 +868,9 @@ async def delete_model_by_id(
@router.delete('/delete/all', response_model=bool) @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) result = await Models.delete_all_models(db=db)
if result: if result:
await publish_event(request, EVENTS.MODEL_DELETED, actor=user, subject_type='model') await publish_event(request, EVENTS.MODEL_DELETED, actor=user, subject_type='model')
+2 -1
View File
@@ -479,7 +479,8 @@ async def pin_note_by_id(
request, request,
EVENTS.NOTE_PINNED if note.is_pinned else EVENTS.NOTE_UNPINNED, EVENTS.NOTE_PINNED if note.is_pinned else EVENTS.NOTE_UNPINNED,
actor=user, actor=user,
subject_id=note.id, subject_type='note', subject_id=note.id,
subject_type='note',
) )
return note return note
+1 -3
View File
@@ -356,9 +356,7 @@ async def update_config(request: Request, form_data: OpenAIConfigForm, user=Depe
@router.post('/audio/speech') @router.post('/audio/speech')
async def speech(request: Request, user=Depends(get_verified_user)): async def speech(request: Request, user=Depends(get_verified_user)):
if user.role != 'admin' and not await has_permission( if user.role != 'admin' and not await has_permission(user.id, 'chat.tts', await Config.get('user.permissions')):
user.id, 'chat.tts', await Config.get('user.permissions')
):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+2 -1
View File
@@ -532,7 +532,8 @@ async def toggle_prompt_active(
request, request,
EVENTS.PROMPT_ENABLED if result.is_active else EVENTS.PROMPT_DISABLED, EVENTS.PROMPT_ENABLED if result.is_active else EVENTS.PROMPT_DISABLED,
actor=user, actor=user,
subject_id=result.id, subject_type='prompt', subject_id=result.id,
subject_type='prompt',
data={'name': result.name, 'command': result.command}, data={'name': result.name, 'command': result.command},
) )
return result return result
+40 -98
View File
@@ -196,9 +196,7 @@ def get_rf(
) )
except Exception as e: except Exception as e:
log.error(f'ExternalReranking: {e}') log.error(f'ExternalReranking: {e}')
raise Exception( raise Exception(ERROR_MESSAGES.DEFAULT(e, 'Error loading reranking model'))
ERROR_MESSAGES.DEFAULT(e, 'Error loading reranking model')
)
else: else:
import sentence_transformers import sentence_transformers
import torch import torch
@@ -516,9 +514,7 @@ async def unload_embedding_model(request: Request):
@router.post('/embedding/update') @router.post('/embedding/update')
async def update_embedding_config(request: Request, form_data: EmbeddingModelUpdateForm, user=Depends(get_admin_user)): async def update_embedding_config(request: Request, form_data: EmbeddingModelUpdateForm, user=Depends(get_admin_user)):
config = await get_retrieval_config() config = await get_retrieval_config()
log.info( log.info(f'Updating embedding model: {config.RAG_EMBEDDING_MODEL} to {form_data.RAG_EMBEDDING_MODEL}')
f'Updating embedding model: {config.RAG_EMBEDDING_MODEL} to {form_data.RAG_EMBEDDING_MODEL}'
)
await unload_embedding_model(request) await unload_embedding_model(request)
try: try:
config.RAG_EMBEDDING_ENGINE = form_data.RAG_EMBEDDING_ENGINE 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, config.RAG_EMBEDDING_BATCH_SIZE,
azure_api_version=( azure_api_version=(
config.RAG_AZURE_OPENAI_API_VERSION config.RAG_AZURE_OPENAI_API_VERSION if config.RAG_EMBEDDING_ENGINE == 'azure_openai' else None
if config.RAG_EMBEDDING_ENGINE == 'azure_openai'
else None
), ),
enable_async=config.ENABLE_ASYNC_EMBEDDING, enable_async=config.ENABLE_ASYNC_EMBEDDING,
concurrent_requests=config.RAG_EMBEDDING_CONCURRENT_REQUESTS, 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)): async def update_rag_config(request: Request, form_data: ConfigForm, user=Depends(get_admin_user)):
# RAG settings # RAG settings
config = await get_retrieval_config() config = await get_retrieval_config()
config.RAG_TEMPLATE = ( config.RAG_TEMPLATE = form_data.RAG_TEMPLATE if form_data.RAG_TEMPLATE is not None else 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.TOP_K = form_data.TOP_K if form_data.TOP_K is not None else config.TOP_K
config.BYPASS_EMBEDDING_AND_RETRIEVAL = ( config.BYPASS_EMBEDDING_AND_RETRIEVAL = (
form_data.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 else config.BYPASS_EMBEDDING_AND_RETRIEVAL
) )
config.RAG_FULL_CONTEXT = ( config.RAG_FULL_CONTEXT = (
form_data.RAG_FULL_CONTEXT form_data.RAG_FULL_CONTEXT if form_data.RAG_FULL_CONTEXT is not None else config.RAG_FULL_CONTEXT
if form_data.RAG_FULL_CONTEXT is not None
else config.RAG_FULL_CONTEXT
) )
# Hybrid search settings # 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 else config.ENABLE_RAG_HYBRID_SEARCH_ENRICHED_TEXTS
) )
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
form_data.TOP_K_RERANKER if form_data.TOP_K_RERANKER is not None else config.TOP_K_RERANKER
)
config.RELEVANCE_THRESHOLD = ( config.RELEVANCE_THRESHOLD = (
form_data.RELEVANCE_THRESHOLD form_data.RELEVANCE_THRESHOLD if form_data.RELEVANCE_THRESHOLD is not None else config.RELEVANCE_THRESHOLD
if form_data.RELEVANCE_THRESHOLD is not None
else config.RELEVANCE_THRESHOLD
) )
config.HYBRID_BM25_WEIGHT = ( config.HYBRID_BM25_WEIGHT = (
form_data.HYBRID_BM25_WEIGHT form_data.HYBRID_BM25_WEIGHT if form_data.HYBRID_BM25_WEIGHT is not None else config.HYBRID_BM25_WEIGHT
if form_data.HYBRID_BM25_WEIGHT is not None
else config.HYBRID_BM25_WEIGHT
) )
# Content extraction settings # Content extraction settings
@@ -981,9 +965,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
else config.CONTENT_EXTRACTION_ENGINE else config.CONTENT_EXTRACTION_ENGINE
) )
config.PDF_EXTRACT_IMAGES = ( config.PDF_EXTRACT_IMAGES = (
form_data.PDF_EXTRACT_IMAGES form_data.PDF_EXTRACT_IMAGES if form_data.PDF_EXTRACT_IMAGES is not None else config.PDF_EXTRACT_IMAGES
if form_data.PDF_EXTRACT_IMAGES is not None
else config.PDF_EXTRACT_IMAGES
) )
config.PDF_LOADER_MODE = ( config.PDF_LOADER_MODE = (
form_data.PDF_LOADER_MODE if form_data.PDF_LOADER_MODE is not None else 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 form_data.TIKA_SERVER_URL if form_data.TIKA_SERVER_URL is not None else config.TIKA_SERVER_URL
) )
config.DOCLING_SERVER_URL = ( config.DOCLING_SERVER_URL = (
form_data.DOCLING_SERVER_URL form_data.DOCLING_SERVER_URL if form_data.DOCLING_SERVER_URL is not None else config.DOCLING_SERVER_URL
if form_data.DOCLING_SERVER_URL is not None
else config.DOCLING_SERVER_URL
) )
config.DOCLING_API_KEY = ( config.DOCLING_API_KEY = (
form_data.DOCLING_API_KEY if form_data.DOCLING_API_KEY is not None else 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 = ( config.DOCLING_PARAMS = form_data.DOCLING_PARAMS if form_data.DOCLING_PARAMS is not None else config.DOCLING_PARAMS
form_data.DOCLING_PARAMS if form_data.DOCLING_PARAMS is not None else config.DOCLING_PARAMS
)
config.DOCUMENT_INTELLIGENCE_ENDPOINT = ( config.DOCUMENT_INTELLIGENCE_ENDPOINT = (
form_data.DOCUMENT_INTELLIGENCE_ENDPOINT form_data.DOCUMENT_INTELLIGENCE_ENDPOINT
if form_data.DOCUMENT_INTELLIGENCE_ENDPOINT is not None 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 else config.MISTRAL_OCR_API_BASE_URL
) )
config.MISTRAL_OCR_API_KEY = ( config.MISTRAL_OCR_API_KEY = (
form_data.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
if form_data.MISTRAL_OCR_API_KEY is not None
else config.MISTRAL_OCR_API_KEY
) )
config.PADDLEOCR_VL_BASE_URL = ( config.PADDLEOCR_VL_BASE_URL = (
form_data.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
if form_data.PADDLEOCR_VL_BASE_URL is not None
else config.PADDLEOCR_VL_BASE_URL
) )
config.PADDLEOCR_VL_TOKEN = ( config.PADDLEOCR_VL_TOKEN = (
form_data.PADDLEOCR_VL_TOKEN form_data.PADDLEOCR_VL_TOKEN if form_data.PADDLEOCR_VL_TOKEN is not None else config.PADDLEOCR_VL_TOKEN
if form_data.PADDLEOCR_VL_TOKEN is not None
else config.PADDLEOCR_VL_TOKEN
) )
# MinerU settings # MinerU settings
config.MINERU_API_MODE = ( config.MINERU_API_MODE = (
form_data.MINERU_API_MODE if form_data.MINERU_API_MODE is not None else 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 = ( config.MINERU_API_URL = form_data.MINERU_API_URL if form_data.MINERU_API_URL is not None else 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_KEY = (
form_data.MINERU_API_KEY if form_data.MINERU_API_KEY is not None else config.MINERU_API_KEY
)
config.MINERU_API_TIMEOUT = ( config.MINERU_API_TIMEOUT = (
form_data.MINERU_API_TIMEOUT form_data.MINERU_API_TIMEOUT if form_data.MINERU_API_TIMEOUT is not None else config.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_PARAMS = form_data.MINERU_PARAMS if form_data.MINERU_PARAMS is not None else config.MINERU_PARAMS
config.MINERU_FILE_EXTENSIONS = ( config.MINERU_FILE_EXTENSIONS = (
form_data.MINERU_FILE_EXTENSIONS form_data.MINERU_FILE_EXTENSIONS
if form_data.MINERU_FILE_EXTENSIONS is not None 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(): if torch.cuda.is_available():
torch.cuda.empty_cache() torch.cuda.empty_cache()
config.RAG_RERANKING_ENGINE = ( config.RAG_RERANKING_ENGINE = (
form_data.RAG_RERANKING_ENGINE form_data.RAG_RERANKING_ENGINE if form_data.RAG_RERANKING_ENGINE is not None else config.RAG_RERANKING_ENGINE
if form_data.RAG_RERANKING_ENGINE is not None
else config.RAG_RERANKING_ENGINE
) )
config.RAG_EXTERNAL_RERANKER_URL = ( 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 else config.RAG_RERANKING_BATCH_SIZE
) )
log.info( log.info(f'Updating reranking model: {config.RAG_RERANKING_MODEL} to {form_data.RAG_RERANKING_MODEL}')
f'Updating reranking model: {config.RAG_RERANKING_MODEL} to {form_data.RAG_RERANKING_MODEL}'
)
try: try:
config.RAG_RERANKING_MODEL = ( config.RAG_RERANKING_MODEL = (
form_data.RAG_RERANKING_MODEL form_data.RAG_RERANKING_MODEL if form_data.RAG_RERANKING_MODEL is not None else config.RAG_RERANKING_MODEL
if form_data.RAG_RERANKING_MODEL is not None
else config.RAG_RERANKING_MODEL
) )
try: try:
if ( if config.ENABLE_RAG_HYBRID_SEARCH and not config.BYPASS_EMBEDDING_AND_RETRIEVAL:
config.ENABLE_RAG_HYBRID_SEARCH
and not config.BYPASS_EMBEDDING_AND_RETRIEVAL
):
request.app.state.rf = get_rf( request.app.state.rf = get_rf(
config.RAG_RERANKING_ENGINE, config.RAG_RERANKING_ENGINE,
config.RAG_RERANKING_MODEL, config.RAG_RERANKING_MODEL,
@@ -1216,25 +1171,17 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
) )
# Chunking settings # Chunking settings
config.TEXT_SPLITTER = ( config.TEXT_SPLITTER = form_data.TEXT_SPLITTER if form_data.TEXT_SPLITTER is not None else 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 = ( config.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER = (
form_data.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER form_data.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER
if form_data.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER is not None if form_data.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER is not None
else config.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER else config.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER
) )
config.CHUNK_SIZE = ( config.CHUNK_SIZE = form_data.CHUNK_SIZE if form_data.CHUNK_SIZE is not None else config.CHUNK_SIZE
form_data.CHUNK_SIZE if form_data.CHUNK_SIZE is not None else config.CHUNK_SIZE
)
config.CHUNK_MIN_SIZE_TARGET = ( config.CHUNK_MIN_SIZE_TARGET = (
form_data.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
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.CHUNK_OVERLAP = form_data.CHUNK_OVERLAP if form_data.CHUNK_OVERLAP is not None else config.CHUNK_OVERLAP
config.RAG_TOKENIZER_MODEL = ( config.RAG_TOKENIZER_MODEL = (
form_data.RAG_TOKENIZER_MODEL.strip() form_data.RAG_TOKENIZER_MODEL.strip()
if form_data.RAG_TOKENIZER_MODEL is not None 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_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_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.WEB_SEARCH_DOMAIN_FILTER_LIST = form_data.web.WEB_SEARCH_DOMAIN_FILTER_LIST
config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = ( config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = form_data.web.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.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.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 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, config.RAG_EMBEDDING_BATCH_SIZE,
azure_api_version=( azure_api_version=(
config.RAG_AZURE_OPENAI_API_VERSION config.RAG_AZURE_OPENAI_API_VERSION if config.RAG_EMBEDDING_ENGINE == 'azure_openai' else None
if config.RAG_EMBEDDING_ENGINE == 'azure_openai'
else None
), ),
enable_async=config.ENABLE_ASYNC_EMBEDDING, enable_async=config.ENABLE_ASYNC_EMBEDDING,
concurrent_requests=config.RAG_EMBEDDING_CONCURRENT_REQUESTS, concurrent_requests=config.RAG_EMBEDDING_CONCURRENT_REQUESTS,
@@ -1997,7 +1940,8 @@ async def process_file(
request, request,
EVENTS.RETRIEVAL_CONTENT_PROCESSED, EVENTS.RETRIEVAL_CONTENT_PROCESSED,
actor=user, actor=user,
subject_id=file.id, subject_type='file', subject_id=file.id,
subject_type='file',
data={'collection_name': None, 'filename': file.filename}, data={'collection_name': None, 'filename': file.filename},
) )
return { return {
@@ -2056,7 +2000,8 @@ async def process_file(
request, request,
EVENTS.RETRIEVAL_CONTENT_PROCESSED, EVENTS.RETRIEVAL_CONTENT_PROCESSED,
actor=user, actor=user,
subject_id=file.id, subject_type='file', subject_id=file.id,
subject_type='file',
data={'collection_name': collection_name, 'filename': file.filename}, data={'collection_name': collection_name, 'filename': file.filename},
) )
return { return {
@@ -2131,7 +2076,8 @@ async def process_text(
request, request,
EVENTS.RETRIEVAL_CONTENT_PROCESSED, EVENTS.RETRIEVAL_CONTENT_PROCESSED,
actor=user, 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]}, data={'name': form_data.name, 'content_preview': text_content[:300]},
) )
return { return {
@@ -2451,11 +2397,7 @@ async def search_web(request: Request, engine: str, query: str, user=None) -> li
config.WEB_SEARCH_DOMAIN_FILTER_LIST, config.WEB_SEARCH_DOMAIN_FILTER_LIST,
) )
elif engine == 'azure': elif engine == 'azure':
if ( if config.AZURE_AI_SEARCH_API_KEY and config.AZURE_AI_SEARCH_ENDPOINT and config.AZURE_AI_SEARCH_INDEX_NAME:
config.AZURE_AI_SEARCH_API_KEY
and config.AZURE_AI_SEARCH_ENDPOINT
and config.AZURE_AI_SEARCH_INDEX_NAME
):
return await asyncio.to_thread( return await asyncio.to_thread(
search_azure, search_azure,
config.AZURE_AI_SEARCH_API_KEY, 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, detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
) )
if user.role != 'admin' and not await has_permission( if user.role != 'admin' and not await has_permission(user.id, 'features.web_search', config.USER_PERMISSIONS):
user.id, 'features.web_search', config.USER_PERMISSIONS
):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -2980,7 +2920,8 @@ async def reset_upload_dir(request: Request, user=Depends(get_admin_user)) -> bo
request, request,
EVENTS.RETRIEVAL_UPLOADS_RESET, EVENTS.RETRIEVAL_UPLOADS_RESET,
actor=user, actor=user,
subject_id='all', subject_type='file.uploads', subject_id='all',
subject_type='file.uploads',
) )
return True return True
@@ -3117,7 +3058,8 @@ async def process_files_batch(
request, request,
EVENTS.RETRIEVAL_CONTENT_PROCESSED, EVENTS.RETRIEVAL_CONTENT_PROCESSED,
actor=user, actor=user,
subject_id=collection_name, subject_type='retrieval.collection', subject_id=collection_name,
subject_type='retrieval.collection',
data={ data={
'count': len([item for item in file_results if item.status == 'completed']), 'count': len([item for item in file_results if item.status == 'completed']),
'errors': len(file_errors), 'errors': len(file_errors),
+2 -6
View File
@@ -159,12 +159,8 @@ async def create_new_skill(
db: AsyncSession = Depends(get_async_session), db: AsyncSession = Depends(get_async_session),
): ):
if user.role != 'admin' and not ( if user.role != 'admin' and not (
await has_permission( await has_permission(user.id, 'workspace.skills', await Config.get('user.permissions'), db=db)
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)
)
or await has_permission(
user.id, 'workspace.skills_import', await Config.get('user.permissions'), db=db
)
): ):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
+1 -2
View File
@@ -76,8 +76,7 @@ async def get_tools(
for tool in await Tools.get_tools(defer_content=True, db=db): for tool in await Tools.get_tools(defer_content=True, db=db):
tool_module = tools_cache.get(tool.id) tool_module = tools_cache.get(tool.id)
has_user_valves = ( has_user_valves = (
hasattr(tool_module, 'UserValves') if tool_module hasattr(tool_module, 'UserValves') if tool_module else (tool.meta.has_user_valves if tool.meta else False)
else (tool.meta.has_user_valves if tool.meta else False)
) )
tools.append( tools.append(
ToolUserResponse( ToolUserResponse(
+4 -2
View File
@@ -280,7 +280,8 @@ async def update_default_user_permissions(request: Request, form_data: UserPermi
request, request,
EVENTS.USER_PERMISSIONS_UPDATED, EVENTS.USER_PERMISSIONS_UPDATED,
actor=user, actor=user,
subject_id='user.permissions', subject_type='config', subject_id='user.permissions',
subject_type='config',
) )
return user_permissions return user_permissions
@@ -691,7 +692,8 @@ async def update_user_by_id(
request, request,
EVENTS.AUTH_PASSWORD_CHANGED, EVENTS.AUTH_PASSWORD_CHANGED,
actor=session_user, actor=session_user,
subject_id=user_id, subject_type='user', subject_id=user_id,
subject_type='user',
source='admin', source='admin',
) )
return updated_user return updated_user
@@ -54,10 +54,7 @@ async def compact_messages_for_request(
messages, previous_summary = _apply_latest_summary_checkpoint(messages) messages, previous_summary = _apply_latest_summary_checkpoint(messages)
token_threshold = _resolve_token_threshold(config['token_threshold'], metadata) token_threshold = _resolve_token_threshold(config['token_threshold'], metadata)
if ( if not _exceeds_token_threshold(messages, system_prompt, previous_summary, token_threshold) or len(messages) <= 3:
not _exceeds_token_threshold(messages, system_prompt, previous_summary, token_threshold)
or len(messages) <= 3
):
return messages, previous_summary, False return messages, previous_summary, False
boundary = _find_compaction_boundary(messages) boundary = _find_compaction_boundary(messages)
+2 -2
View File
@@ -355,9 +355,9 @@ async def add_memory_context(request, form_data: dict, user, model: dict | None
seen_ids.add(memory_id) seen_ids.add(memory_id)
content = str(doc) 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 :] 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) sections[Memories.normalize_memory_type(metadata.get('type'))].append(label)
parts = [] parts = []
+10 -11
View File
@@ -1476,6 +1476,7 @@ async def chat_completion_tools_handler(
return body, {'sources': sources} return body, {'sources': sources}
async def chat_web_search_handler(request: Request, form_data: dict, extra_params: dict, user): async def chat_web_search_handler(request: Request, form_data: dict, extra_params: dict, user):
event_emitter = extra_params['__event_emitter__'] event_emitter = extra_params['__event_emitter__']
await event_emitter( await event_emitter(
@@ -3636,9 +3637,7 @@ async def non_streaming_chat_response_handler(response, ctx):
'end_tag': '</think>', 'end_tag': '</think>',
'attributes': {'type': 'reasoning_content'}, 'attributes': {'type': 'reasoning_content'},
'content': ( 'content': (
[{'type': 'output_text', 'text': reasoning_content}] [{'type': 'output_text', 'text': reasoning_content}] if reasoning_content else []
if reasoning_content
else []
), ),
'summary': None, 'summary': None,
} }
@@ -4148,19 +4147,19 @@ async def streaming_chat_response_handler(response, ctx):
# error events so frontend and DB paths still receive them. # error events so frontend and DB paths still receive them.
try: try:
raw_obj = json.loads(data) 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: if raw_error:
try: try:
Chats.upsert_message_to_chat_by_id_and_message_id( Chats.upsert_message_to_chat_by_id_and_message_id(
metadata["chat_id"], metadata['chat_id'],
metadata["message_id"], metadata['message_id'],
{ {
"error": {"content": raw_error}, 'error': {'content': raw_error},
}, },
) )
except Exception: except Exception:
pass pass
await event_emitter({ "type": "chat:completion", "data": { "error": raw_error } }) await event_emitter({'type': 'chat:completion', 'data': {'error': raw_error}})
except Exception: except Exception:
pass pass
continue continue
@@ -4390,9 +4389,9 @@ async def streaming_chat_response_handler(response, ctx):
current_response_tool_call['function']['name'] = delta_name current_response_tool_call['function']['name'] = delta_name
if delta_arguments: if delta_arguments:
current_response_tool_call['function'][ current_response_tool_call['function']['arguments'] += (
'arguments' delta_arguments
] += delta_arguments )
# Emit pending tool calls in real-time # Emit pending tool calls in real-time
if response_tool_calls: if response_tool_calls:
+1 -6
View File
@@ -254,12 +254,7 @@ def convert_output_to_messages(
def flush_pending(): def flush_pending():
nonlocal pending_content, pending_tool_calls, pending_reasoning, pending_reasoning_details nonlocal pending_content, pending_tool_calls, pending_reasoning, pending_reasoning_details
if ( if not pending_content and not pending_tool_calls and not pending_reasoning and not pending_reasoning_details:
not pending_content
and not pending_tool_calls
and not pending_reasoning
and not pending_reasoning_details
):
return return
message = { message = {
+2 -10
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): async def get_all_base_models(request: Request, user: UserModel = None):
config = await Config.get_many('openai.enable', 'ollama.enable') config = await Config.get_many('openai.enable', 'ollama.enable')
openai_task = ( openai_task = fetch_openai_models(request, user) if config.get('openai.enable') else asyncio.sleep(0, result=[])
fetch_openai_models(request, user) ollama_task = fetch_ollama_models(request, user) if config.get('ollama.enable') else asyncio.sleep(0, result=[])
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) function_task = get_function_models(request)
openai_models, ollama_models, function_models = await asyncio.gather(openai_task, ollama_task, function_task) openai_models, ollama_models, function_models = await asyncio.gather(openai_task, ollama_task, function_task)
+3 -11
View File
@@ -178,10 +178,7 @@ def _default_value(value):
async def get_oauth_runtime_config() -> SimpleNamespace: async def get_oauth_runtime_config() -> SimpleNamespace:
keys = [key for key, _default in OAUTH_RUNTIME_CONFIG.values()] keys = [key for key, _default in OAUTH_RUNTIME_CONFIG.values()]
stored = await Config.get_many(*keys) stored = await Config.get_many(*keys)
values = { values = {name: stored.get(key, _default_value(default)) for name, (key, default) in OAUTH_RUNTIME_CONFIG.items()}
name: stored.get(key, _default_value(default))
for name, (key, default) in OAUTH_RUNTIME_CONFIG.items()
}
return SimpleNamespace(**values) 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: def scope_has_resource_indicator(scope: str | None) -> bool:
if not scope: if not scope:
return False return False
return any( return any(scope_value.startswith(('https://', 'http://', 'api://')) for scope_value in scope.split())
scope_value.startswith(('https://', 'http://', 'api://'))
for scope_value in scope.split()
)
def should_send_oauth_resource(client_info: OAuthClientInformationFull | None) -> bool: def should_send_oauth_resource(client_info: OAuthClientInformationFull | None) -> bool:
@@ -852,9 +846,7 @@ class OAuthClientManager:
try: try:
oauth_client_info = resolve_oauth_client_info(connection) oauth_client_info = resolve_oauth_client_info(connection)
oauth_client_info = await recover_static_oauth_client_metadata( oauth_client_info = await recover_static_oauth_client_metadata(connection, oauth_client_info)
connection, oauth_client_info
)
oauth_client_info = apply_connection_oauth_options(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'] return self.add_client(expected_client_id, OAuthClientInformationFull(**oauth_client_info))['client']
except Exception as e: except Exception as e:
+3 -9
View File
@@ -217,9 +217,7 @@ async def load_tool_module_by_id(tool_id, content=None):
# Install required packages found within the frontmatter. # Install required packages found within the frontmatter.
# Runs `pip install` via subprocess, which can take a long time; # Runs `pip install` via subprocess, which can take a long time;
# offload to a thread so it doesn't block the event loop. # offload to a thread so it doesn't block the event loop.
await asyncio.to_thread( await asyncio.to_thread(install_frontmatter_requirements, frontmatter.get('requirements', ''))
install_frontmatter_requirements, frontmatter.get('requirements', '')
)
module_name = f'tool_{tool_id}' module_name = f'tool_{tool_id}'
module = types.ModuleType(module_name) module = types.ModuleType(module_name)
@@ -264,9 +262,7 @@ async def load_function_module_by_id(function_id: str, content: str | None = Non
else: else:
frontmatter = extract_frontmatter(content) frontmatter = extract_frontmatter(content)
# `pip install` via subprocess can block for a long time; offload it. # `pip install` via subprocess can block for a long time; offload it.
await asyncio.to_thread( await asyncio.to_thread(install_frontmatter_requirements, frontmatter.get('requirements', ''))
install_frontmatter_requirements, frontmatter.get('requirements', '')
)
module_name = f'function_{function_id}' module_name = f'function_{function_id}'
module = types.ModuleType(module_name) module = types.ModuleType(module_name)
@@ -468,8 +464,6 @@ async def install_tool_and_function_dependencies():
all_dependencies += f'{dependencies}, ' all_dependencies += f'{dependencies}, '
# `pip install` via subprocess can block for a long time; offload it. # `pip install` via subprocess can block for a long time; offload it.
await asyncio.to_thread( await asyncio.to_thread(install_frontmatter_requirements, all_dependencies.strip(', '))
install_frontmatter_requirements, all_dependencies.strip(', ')
)
except Exception as e: except Exception as e:
log.error(f'Error installing requirements: {e}') log.error(f'Error installing requirements: {e}')
+3 -15
View File
@@ -618,19 +618,11 @@ async def get_builtin_tools(
builtin_functions.append(execute_code) builtin_functions.append(execute_code)
# Notes tools - search, view, create, and update user's notes # Notes tools - search, view, create, and update user's notes
if ( if is_builtin_tool_enabled('notes') and config.get('notes.enable') and await has_user_permission('notes'):
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]) builtin_functions.extend([search_notes, view_note, write_note, replace_note_content])
# Channels tools - search channels and messages # Channels tools - search channels and messages
if ( if is_builtin_tool_enabled('channels') and config.get('channels.enable') and await has_user_permission('channels'):
is_builtin_tool_enabled('channels')
and config.get('channels.enable')
and await has_user_permission('channels')
):
builtin_functions.extend( builtin_functions.extend(
[ [
search_channels, search_channels,
@@ -659,11 +651,7 @@ async def get_builtin_tools(
) )
# Calendar tools - search/create/update/delete events # Calendar tools - search/create/update/delete events
if ( if is_builtin_tool_enabled('calendar') and config.get('calendar.enable') and await has_user_permission('calendar'):
is_builtin_tool_enabled('calendar')
and config.get('calendar.enable')
and await has_user_permission('calendar')
):
builtin_functions.extend( builtin_functions.extend(
[search_calendar_events, create_calendar_event, update_calendar_event, delete_calendar_event] [search_calendar_events, create_calendar_event, update_calendar_event, delete_calendar_event]
) )
+1 -3
View File
@@ -27,9 +27,7 @@ def _event_text(message: str, description: str | None = None, event_data: dict |
return '\n'.join(lines) return '\n'.join(lines)
async def post_webhook( async def post_webhook(name: str, url: str, message: str, event_data: dict, description: str | None = None) -> bool:
name: str, url: str, message: str, event_data: dict, description: str | None = None
) -> bool:
try: try:
log.debug(f'post_webhook: {url}, {message}, {event_data}') log.debug(f'post_webhook: {url}, {message}, {event_data}')
# Block private-IP / loopback / cloud-metadata targets — the URL is # Block private-IP / loopback / cloud-metadata targets — the URL is
+8 -2
View File
@@ -1156,7 +1156,12 @@ export const getChatAccessGrants = async (token: string, id: string) => {
return res; 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; let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, { 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}` }) ...(token && { authorization: `Bearer ${token}` })
}, },
body: JSON.stringify({ body: JSON.stringify({
chat: chat chat: chat,
...(deletedMessageIds.length && { deleted_message_ids: deletedMessageIds })
}) })
}) })
.then(async (res) => { .then(async (res) => {
+4 -2
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)); const _chatId = JSON.parse(JSON.stringify($chatId));
let _messageId = JSON.parse(JSON.stringify(message.id)); let _messageId = JSON.parse(JSON.stringify(message.id));
@@ -545,7 +545,9 @@
await tick(); await tick();
await tick(); await tick();
saveChatHandler(_chatId, history); if (save) {
saveChatHandler(_chatId, history);
}
}; };
const updateLastReadAt = (id) => { const updateLastReadAt = (id) => {
+14 -5
View File
@@ -35,6 +35,7 @@
export let atSelectedModel; export let atSelectedModel;
let messages = []; let messages = [];
let deletedMessageIds = new Set<string>();
export let setInputText: Function = () => {}; export let setInputText: Function = () => {};
@@ -168,10 +169,16 @@
if (!$temporaryChatEnabled) { if (!$temporaryChatEnabled) {
history = history; history = history;
await tick(); await tick();
const res = await updateChatById(localStorage.token, chatId, { const res = await updateChatById(
history: history, localStorage.token,
messages: messages chatId,
}); {
history: history,
messages: messages
},
Array.from(deletedMessageIds)
);
deletedMessageIds.clear();
// Refresh local message content from backend (e.g. re-derived via serialize_output) // Refresh local message content from backend (e.g. re-derived via serialize_output)
if (res?.chat?.history?.messages) { if (res?.chat?.history?.messages) {
@@ -461,9 +468,11 @@
// Delete the message and its children // Delete the message and its children
[messageId, ...childMessageIds].forEach((id) => { [messageId, ...childMessageIds].forEach((id) => {
delete history.messages[id]; delete history.messages[id];
deletedMessageIds.add(id);
}); });
showMessage({ id: parentMessageId }, false); showMessage({ id: parentMessageId }, false, false);
await updateChat();
}; };
const triggerScroll = () => { const triggerScroll = () => {