diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index 1b7f67ba50..95d7ca962d 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -789,7 +789,9 @@ async def resume_stream(sid, data): # Cap payload bytes. Keep the newest entries that fit; older ones # are either already in the DB-backed content or will arrive via the - # final done:True checkpoint. + # final done:True checkpoint. A single envelope larger than the cap + # is skipped rather than allowed through — forcing it past the cap + # would still exceed Socket.IO's buffer. total_bytes = 0 capped = [] for env in reversed(envelopes): @@ -797,7 +799,9 @@ async def resume_stream(sid, data): size = len(json.dumps(env)) except Exception: continue - if total_bytes + size > RESUME_STREAM_REPLAY_MAX_BYTES and capped: + if size > RESUME_STREAM_REPLAY_MAX_BYTES: + continue + if total_bytes + size > RESUME_STREAM_REPLAY_MAX_BYTES: break capped.append(env) total_bytes += size diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index c5dc02b012..68053ccd4e 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -451,12 +451,15 @@ let message = history.messages[event.message_id]; if (message) { - // Buffer live frames during replay; _replayed frames skip the fence. - // Store the ack callback alongside the event so Socket.IO - // call-style events (confirmation/execute/input) don't lose - // their response path when buffered and later replayed. + // Buffer live frames during replay; _replayed frames skip + // the fence. Call-style events that carry an ack callback + // ALSO skip the fence — the server is waiting on the ack + // and buffering could exceed WEBSOCKET_EVENT_CALLER_TIMEOUT + // (up to RESUME_FENCE_TIMEOUT_MS of delay). Ack events + // don't carry seq and don't mutate streamed content, so + // bypassing is safe against the original replay race. const queue = resumeQueueByMessageId.get(event.message_id); - if (queue && !event?._replayed) { + if (queue && !event?._replayed && !cb) { queue.push({ event, cb }); return; } @@ -701,7 +704,9 @@ message.id, setTimeout(() => { console.warn('resume-stream fence timed out for', message.id); - clearResumeFence(message.id); + clearResumeFence(message.id).catch((e) => + console.error('resume fence timeout flush failed', e) + ); }, RESUME_FENCE_TIMEOUT_MS) ); const requestId =