diff --git a/backend/open_webui/models/apps.py b/backend/open_webui/models/apps.py index 4646d08694..9be808e288 100644 --- a/backend/open_webui/models/apps.py +++ b/backend/open_webui/models/apps.py @@ -124,8 +124,8 @@ class AppUpdateForm(StrictModel): access_grants: list[dict] | None = None -class AppContextForm(StrictModel): - context: str = Field(max_length=100000) +class AppTokenForm(StrictModel): + token: str = Field(max_length=100000) class AppConnectionForm(StrictModel): diff --git a/backend/open_webui/routers/apps.py b/backend/open_webui/routers/apps.py index b0650441de..602500bd4f 100644 --- a/backend/open_webui/routers/apps.py +++ b/backend/open_webui/routers/apps.py @@ -12,12 +12,12 @@ from open_webui.models.apps import ( AppAccessResponse, AppChannelMessage, AppConnectionForm, - AppContextForm, AppForm, AppInteractionForm, AppManifestForm, AppResponse, Apps, + AppTokenForm, AppUpdateForm, Manifest, ) @@ -30,7 +30,7 @@ from open_webui.utils.apps import ( check_app_invocation_access, create_app_message, decode_app_context_token, - generate_app_response, + generate_app_channel_reply, get_app_by_id, get_app_encryption_cipher, get_app_message_data, @@ -187,9 +187,9 @@ async def get_app_channels(app=Depends(get_verified_app), db: AsyncSession = Dep @router.post('/runtime/authorize') async def authorize_app_execution( - form_data: AppContextForm, app=Depends(get_verified_app), db: AsyncSession = Depends(get_async_session) + form_data: AppTokenForm, app=Depends(get_verified_app), db: AsyncSession = Depends(get_async_session) ): - value = decode_app_context_token(app, 'execution', form_data.context) + value = decode_app_context_token(app, 'execution', form_data.token) user = await Users.get_user_by_id(value['actor_id'], db=db) if not user: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Actor is unavailable') @@ -212,9 +212,9 @@ async def authorize_app_execution( @router.post('/runtime/events/verify') async def verify_app_event( - form_data: AppContextForm, app=Depends(get_verified_app), db: AsyncSession = Depends(get_async_session) + form_data: AppTokenForm, app=Depends(get_verified_app), db: AsyncSession = Depends(get_async_session) ): - value = decode_app_context_token(app, 'event', form_data.context) + value = decode_app_context_token(app, 'event', form_data.token) await check_app_channel_membership(app, value['channel']['id'], db=db) require_scope(app, 'messages:read') return {k: v for k, v in value.items() if k not in ('purpose', 'exp', 'nonce')} @@ -450,7 +450,7 @@ async def create_app_interaction( action = next((item for item in actions if item['action_id'] == form_data.action_id), None) if not action: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Unknown action') - return await generate_app_response( + return await generate_app_channel_reply( app, user, message.channel_id, @@ -479,11 +479,11 @@ async def get_app_operation( if value['actor_id'] != user.id or value['operation_id'] != operation_id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Invalid operation receipt') await check_app_invocation_access(app, value['channel_id'], user, db=db) - return await generate_app_response( + return await generate_app_channel_reply( app, user, value['channel_id'], - 'operation_status', + 'channel.operation.status', {'operation_id': operation_id}, operation_id=operation_id, db=db, diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py index 6358d5274b..82f563634c 100644 --- a/backend/open_webui/routers/channels.py +++ b/backend/open_webui/routers/channels.py @@ -52,7 +52,7 @@ from open_webui.tasks import create_task from open_webui.utils.access_control import filter_allowed_access_grants, has_permission from open_webui.utils.apps import ( check_app_channel_access, - generate_app_response, + generate_app_channel_reply, get_app_by_id, get_app_response, has_app_access, @@ -1090,7 +1090,7 @@ async def send_notification(request, channel, message, active_user_ids, db=None) return True -async def app_response_handler(channel_id, message_id, user_id): +async def app_channel_reply_handler(channel_id, message_id, user_id): """Committed human messages only; errors never cause the human POST to retry.""" if not await Config.get('apps.enable'): return @@ -1110,7 +1110,7 @@ async def app_response_handler(channel_id, message_id, user_id): user = await Users.get_user_by_id(user_id, db=db) if not user: continue - value = await generate_app_response( + value = await generate_app_channel_reply( app, user, channel_id, @@ -1135,7 +1135,7 @@ async def app_response_handler(channel_id, message_id, user_id): 'channel_id': channel_id, 'message_id': message_id, 'parent_id': parent_id, - 'response': value, + 'reply': value, }, [user_id], ) @@ -1409,7 +1409,7 @@ async def post_new_message( # Background tasks should manage their own short-lived sessions to avoid # holding database connections during slow operations (e.g., LLM calls). async def background_handler(): - await create_task(None, app_response_handler(channel.id, message.id, user.id)) + await create_task(None, app_channel_reply_handler(channel.id, message.id, user.id)) await model_response_handler(request, channel, message, user) await send_notification( request, diff --git a/backend/open_webui/utils/apps.py b/backend/open_webui/utils/apps.py index 27172efcf0..fac4cfa0c5 100644 --- a/backend/open_webui/utils/apps.py +++ b/backend/open_webui/utils/apps.py @@ -375,7 +375,7 @@ async def send_app_message_event(message, kind, created_at): 'webui_url': app.data['webui_url'], 'channel': {'id': message['channel_id']}, } - envelope['event_context'] = create_app_context_token(app, 'event', envelope, expires_in=300) + envelope['event_token'] = create_app_context_token(app, 'event', envelope, expires_in=300) status, _ = await send_app_event(app, envelope, timeout=10) if not 200 <= status < 300: log.warning('App %s did not acknowledge %s: HTTP %s', app_id, kind, status) @@ -452,13 +452,13 @@ async def update_app_message(db, app, message_id, form, if_match): return value -async def generate_app_response( +async def generate_app_channel_reply( app, user, channel_id, event_type, data, *, db: AsyncSession, source=None, operation_id=None, scope=None ): await check_app_invocation_access(app, channel_id, user, db=db) require_scope(app, scope) operation_id = operation_id or str(uuid.uuid4()) - context = create_app_context_token( + token = create_app_context_token( app, 'execution', { @@ -477,7 +477,7 @@ async def generate_app_response( 'actor': {'id': user.id, 'is_admin': user.role == 'admin'}, 'channel': {'id': channel_id}, 'data': data, - 'execution_context': context, + 'execution_token': token, 'source': source, } checked_key = (app.api_key, app.data['manifest']['public_key']) @@ -500,7 +500,7 @@ async def generate_app_response( except Exception: raise HTTPException(502, 'App returned an invalid response') from None await check_app_invocation_access(app, channel_id, user, db=db) - if event_type == 'operation_status' and (reply.visibility != 'private' or reply.type == 'update'): + if event_type == 'channel.operation.status' and (reply.visibility != 'private' or reply.type == 'update'): raise HTTPException(502, 'Deferred results must be private; apps publish separately through the message API') if reply.actions and (reply.visibility != 'channel' or reply.type not in ('message', 'update')): raise HTTPException(502, 'Buttons require a channel message') diff --git a/src/lib/components/channel/AppResponse.svelte b/src/lib/components/channel/AppChannelReply.svelte similarity index 74% rename from src/lib/components/channel/AppResponse.svelte rename to src/lib/components/channel/AppChannelReply.svelte index 1312f8a403..2284f91427 100644 --- a/src/lib/components/channel/AppResponse.svelte +++ b/src/lib/components/channel/AppChannelReply.svelte @@ -4,7 +4,7 @@ import Markdown from '$lib/components/chat/Messages/Markdown.svelte'; export let appId: string; export let channelId: string; - export let response: any; + export let reply: any; export let source: any = null; const dispatch = createEventDispatcher(); let busy = false, @@ -22,12 +22,12 @@ action_id: actionId }); if (result.visibility !== 'channel' || result.type === 'error') { - dispatch('response', { + dispatch('reply', { app_id: appId, app_name: source.user?.name, channel_id: channelId, message_id: source.id, - response: result + reply: result }); } } catch (e) { @@ -37,17 +37,17 @@ } } async function poll() { - if (disposed || response?.type !== 'deferred') return; + if (disposed || reply?.type !== 'deferred') return; if (!document.hidden) { try { const result = await getAppOperationById( localStorage.token, appId, - response.operation_id, - response.receipt + reply.operation_id, + reply.receipt ); if (disposed) return; - response = result; + reply = result; } catch (e) { error = String(e); return; @@ -58,16 +58,16 @@ } function download() { const url = URL.createObjectURL( - new Blob([response.content], { type: 'text/markdown;charset=utf-8' }) + new Blob([reply.content], { type: 'text/markdown;charset=utf-8' }) ); const link = document.createElement('a'); link.href = url; - link.download = response.filename; + link.download = reply.filename; link.click(); URL.revokeObjectURL(url); } onMount(() => { - if (response?.type === 'deferred') timer = setTimeout(poll, 1000); + if (reply?.type === 'deferred') timer = setTimeout(poll, 1000); }); onDestroy(() => { disposed = true; @@ -76,20 +76,20 @@
+ {#if reply?.title}
The app could not complete this operation.
{/if} {#if error}{error}
{/if} - {#if response?.type === 'deferred'}Working…
{/if} - {#if response?.type === 'download'}{/if} - {#if response?.actions?.length} + {#if reply?.actions?.length}