From 2fccf3a3be37c33b92f587039b07b57be228a9c7 Mon Sep 17 00:00:00 2001 From: uinstinct <61635505+uinstinct@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:30:18 +0530 Subject: [PATCH 1/2] feat: retry overloaded errors --- gui/src/redux/thunks/streamThunkWrapper.tsx | 78 +++++++++++++-------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/gui/src/redux/thunks/streamThunkWrapper.tsx b/gui/src/redux/thunks/streamThunkWrapper.tsx index 4454246cd7d..1e032332ca4 100644 --- a/gui/src/redux/thunks/streamThunkWrapper.tsx +++ b/gui/src/redux/thunks/streamThunkWrapper.tsx @@ -8,43 +8,61 @@ import { ThunkApiType } from "../store"; import { cancelStream } from "./cancelStream"; import { saveCurrentSession } from "./session"; +const OVERLOADED_RETRIES = 3; +const OVERLOADED_DELAY_MS = 1000; + +function isOverloadedErrorMessage(message?: string | null): boolean { + if (!message) return false; + const lower = message.toLowerCase(); + return lower.includes("overloaded") || lower.includes("malformed json"); +} + export const streamThunkWrapper = createAsyncThunk< void, () => Promise, ThunkApiType ->("chat/streamWrapper", async (runStream, { dispatch, extra, getState }) => { - try { - await runStream(); - const state = getState(); - if (!state.session.isInEdit) { - await dispatch( - saveCurrentSession({ - openNewSession: false, - generateTitle: true, - }), - ); - } - } catch (e) { - await dispatch(cancelStream()); - dispatch(setDialogMessage()); - dispatch(setShowDialog(true)); +>("chat/streamWrapper", async (runStream, { dispatch, getState }) => { + for (let attempt = 0; attempt <= OVERLOADED_RETRIES; attempt++) { + try { + await runStream(); + const state = getState(); + if (!state.session.isInEdit) { + await dispatch( + saveCurrentSession({ + openNewSession: false, + generateTitle: true, + }), + ); + } + return; + } catch (e) { + // Get the selected model from the state for error analysis + const state = getState(); + const selectedModel = selectSelectedChatModel(state); + const { parsedError, statusCode, message, modelTitle, providerName } = + analyzeError(e, selectedModel); - // Get the selected model from the state for error analysis - const state = getState(); - const selectedModel = selectSelectedChatModel(state); + const shouldRetry = + isOverloadedErrorMessage(message) && attempt < OVERLOADED_RETRIES; - const { parsedError, statusCode, modelTitle, providerName } = analyzeError( - e, - selectedModel, - ); + if (shouldRetry) { + await dispatch(cancelStream()); + const delayMs = OVERLOADED_DELAY_MS * 2 ** attempt; + await new Promise((resolve) => setTimeout(resolve, delayMs)); + await dispatch(cancelStream()); + } else { + dispatch(setDialogMessage()); + dispatch(setShowDialog(true)); - const errorData = { - error_type: statusCode ? `HTTP ${statusCode}` : "Unknown", - error_message: parsedError, - model_provider: providerName, - model_title: modelTitle, - }; + const errorData = { + error_type: statusCode ? `HTTP ${statusCode}` : "Unknown", + error_message: parsedError, + model_provider: providerName, + model_title: modelTitle, + }; - posthog.capture("gui_stream_error", errorData); + posthog.capture("gui_stream_error", errorData); + } + } } }); From c391353709ad4367fb333680190e0ba73c868ef0 Mon Sep 17 00:00:00 2001 From: "continue[bot]" Date: Tue, 9 Dec 2025 14:06:27 +0000 Subject: [PATCH 2/2] fix: add missing cancelStream call and return for non-retryable errors - Add cancelStream() call before showing error dialog for non-retryable errors - Add return statement to prevent loop continuation after handling non-retryable error Co-authored-by: nate Generated with [Continue](https://continue.dev) Co-Authored-By: Continue --- gui/src/redux/thunks/streamThunkWrapper.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/src/redux/thunks/streamThunkWrapper.tsx b/gui/src/redux/thunks/streamThunkWrapper.tsx index 1e032332ca4..2cf30306573 100644 --- a/gui/src/redux/thunks/streamThunkWrapper.tsx +++ b/gui/src/redux/thunks/streamThunkWrapper.tsx @@ -51,6 +51,7 @@ export const streamThunkWrapper = createAsyncThunk< await new Promise((resolve) => setTimeout(resolve, delayMs)); await dispatch(cancelStream()); } else { + await dispatch(cancelStream()); dispatch(setDialogMessage()); dispatch(setShowDialog(true)); @@ -62,6 +63,7 @@ export const streamThunkWrapper = createAsyncThunk< }; posthog.capture("gui_stream_error", errorData); + return; } } }