@@ -13,6 +13,7 @@ import {
1313 buildThreadErrorDiagnosticsCopy ,
1414 humanizeThreadError ,
1515 isAuthenticationThreadError ,
16+ isOutOfMemoryThreadError ,
1617} from "./threadError" ;
1718import {
1819 getProviderStatusHeading ,
@@ -33,6 +34,8 @@ interface ErrorNotificationBarProps {
3334 includeDiagnosticsTipsInCopy ?: boolean ;
3435 /** Dismiss the thread error */
3536 onDismissThreadError ?: ( ) => void ;
37+ /** Reset a provider session after an OOM failure */
38+ onRecoverFromOutOfMemory ?: ( ) => void ;
3639 /** Provider health status */
3740 providerStatus : ServerProviderStatus | null ;
3841 /** Companion transport state (only relevant for mobile companion) */
@@ -49,6 +52,9 @@ interface NotificationItem {
4952 description : string ;
5053 detailsText ?: string | null ;
5154 diagnosticsCopyText ?: string | null ;
55+ actionLabel ?: string ;
56+ actionAriaLabel ?: string ;
57+ onAction ?: ( ) => void ;
5258 severity : "error" | "warning" | "info" ;
5359 dismissible : boolean ;
5460 onDismiss ?: ( ) => void ;
@@ -64,6 +70,7 @@ export const ErrorNotificationBar = memo(function ErrorNotificationBar({
6470 showNotificationDetails = false ,
6571 includeDiagnosticsTipsInCopy = false ,
6672 onDismissThreadError,
73+ onRecoverFromOutOfMemory,
6774 providerStatus,
6875 transportState,
6976 isMobileCompanion,
@@ -133,6 +140,8 @@ export const ErrorNotificationBar = memo(function ErrorNotificationBar({
133140 if ( threadError ) {
134141 if ( showAuthFailuresAsErrors || ! isAuthenticationThreadError ( threadError ) ) {
135142 const presentation = humanizeThreadError ( threadError ) ;
143+ const showOutOfMemoryRecovery =
144+ isOutOfMemoryThreadError ( threadError ) && onRecoverFromOutOfMemory !== undefined ;
136145 items . push ( {
137146 id : buildThreadErrorNotificationId ( threadError ) ,
138147 kind : "thread-error" ,
@@ -143,6 +152,13 @@ export const ErrorNotificationBar = memo(function ErrorNotificationBar({
143152 diagnosticsCopyText : buildThreadErrorDiagnosticsCopy ( threadError , {
144153 includeTips : includeDiagnosticsTipsInCopy ,
145154 } ) ,
155+ ...( showOutOfMemoryRecovery
156+ ? {
157+ actionLabel : "Reset session" ,
158+ actionAriaLabel : "Reset session after out-of-memory failure" ,
159+ onAction : onRecoverFromOutOfMemory ,
160+ }
161+ : { } ) ,
146162 severity : "error" ,
147163 dismissible : ! ! onDismissThreadError ,
148164 ...( onDismissThreadError ? { onDismiss : onDismissThreadError } : { } ) ,
@@ -156,6 +172,7 @@ export const ErrorNotificationBar = memo(function ErrorNotificationBar({
156172 showAuthFailuresAsErrors ,
157173 includeDiagnosticsTipsInCopy ,
158174 onDismissThreadError ,
175+ onRecoverFromOutOfMemory ,
159176 providerStatus ,
160177 transportState ,
161178 isMobileCompanion ,
@@ -221,6 +238,9 @@ export const ErrorNotificationBar = memo(function ErrorNotificationBar({
221238 if ( visibleNotifications . length === 0 ) return null ;
222239
223240 const primary = visibleNotifications [ 0 ] ! ;
241+ const actionNotification = visibleNotifications . find (
242+ ( notification ) => notification . onAction && notification . actionLabel ,
243+ ) ;
224244 const PrimaryIcon = primary . icon ;
225245 const count = visibleNotifications . length ;
226246 const countLabel = count === 1 ? "1 notification" : `${ count } notifications` ;
@@ -261,6 +281,18 @@ export const ErrorNotificationBar = memo(function ErrorNotificationBar({
261281 </ span >
262282
263283 < div className = "flex shrink-0 items-center gap-1" >
284+ { actionNotification ?. onAction && actionNotification . actionLabel ? (
285+ < Button
286+ type = "button"
287+ variant = "outline"
288+ size = "xs"
289+ aria-label = { actionNotification . actionAriaLabel ?? actionNotification . actionLabel }
290+ className = "min-w-0 px-2 text-[10px] font-medium"
291+ onClick = { actionNotification . onAction }
292+ >
293+ { actionNotification . actionLabel }
294+ </ Button >
295+ ) : null }
264296 < Button
265297 type = "button"
266298 variant = "outline"
@@ -313,6 +345,18 @@ export const ErrorNotificationBar = memo(function ErrorNotificationBar({
313345 ) : null }
314346 </ div >
315347 < div className = "mt-0.5 flex shrink-0 items-center gap-1" >
348+ { notif . onAction && notif . actionLabel ? (
349+ < Button
350+ type = "button"
351+ variant = "outline"
352+ size = "xs"
353+ aria-label = { notif . actionAriaLabel ?? notif . actionLabel }
354+ className = "h-6 px-2 text-[10px]"
355+ onClick = { notif . onAction }
356+ >
357+ { notif . actionLabel }
358+ </ Button >
359+ ) : null }
316360 { notif . kind === "thread-error" && notif . diagnosticsCopyText ? (
317361 < MessageCopyButton
318362 text = { notif . diagnosticsCopyText }
0 commit comments