@@ -175,8 +175,14 @@ def _get_next_reconnection_delay(self, attempt: int) -> float:
175175 Returns:
176176 Time to wait in seconds before next reconnection attempt
177177 """
178- # TODO: Implement proper delay calculation with server retry for SEP-1699
179- return 1.0
178+ # Use server-provided retry value if available
179+ if self ._server_retry_seconds is not None :
180+ return self ._server_retry_seconds
181+
182+ # Fall back to exponential backoff
183+ opts = self .reconnection_options
184+ delay = opts .initial_reconnection_delay * (opts .reconnection_delay_grow_factor ** attempt )
185+ return min (delay , opts .max_reconnection_delay )
180186
181187 async def _handle_sse_event (
182188 self ,
@@ -376,25 +382,39 @@ async def _handle_sse_response(
376382 attempt : int = 0 ,
377383 ) -> None :
378384 """Handle SSE response from the server with automatic reconnection."""
385+ has_priming_event = False
386+ last_event_id : str | None = None
387+ is_complete = False
388+
379389 try :
380390 event_source = EventSource (response )
381391 async for sse in event_source .aiter_sse (): # pragma: no branch
382- is_complete , _has_event_id = await self ._handle_sse_event (
392+ is_complete , has_event_id = await self ._handle_sse_event (
383393 sse ,
384394 ctx .read_stream_writer ,
385395 resumption_callback = (ctx .metadata .on_resumption_token_update if ctx .metadata else None ),
386396 is_initialization = is_initialization ,
387397 )
388398
399+ # Track priming events
400+ if has_event_id :
401+ has_priming_event = True
402+ last_event_id = sse .id
403+
389404 # If the SSE event indicates completion, like returning response/error
390405 # break the loop
391406 if is_complete :
392407 await response .aclose ()
393408 break
394409 except Exception as e : # pragma: no cover
395410 logger .exception ("Error reading SSE stream:" )
396- await ctx .read_stream_writer .send (e )
397- # TODO: Implement auto-reconnection for SEP-1699
411+ # Don't send exception if we can reconnect
412+ if not (has_priming_event and last_event_id ):
413+ await ctx .read_stream_writer .send (e )
414+
415+ # Auto-reconnect if stream ended without completion and we have priming event
416+ if not is_complete and has_priming_event and last_event_id : # pragma: no cover
417+ await self ._attempt_sse_reconnection (ctx , last_event_id , attempt )
398418
399419 async def _attempt_sse_reconnection ( # pragma: no cover
400420 self ,
@@ -407,8 +427,42 @@ async def _attempt_sse_reconnection( # pragma: no cover
407427 Called when SSE stream ends without receiving a response/error,
408428 but we have a priming event indicating resumability.
409429 """
410- # TODO: Implement SSE reconnection for SEP-1699
411- pass
430+ max_retries = self .reconnection_options .max_retries
431+
432+ if attempt >= max_retries :
433+ error_msg = f"Max reconnection attempts ({ max_retries } ) exceeded"
434+ logger .error (error_msg )
435+ await ctx .read_stream_writer .send (StreamableHTTPError (error_msg ))
436+ return
437+
438+ # Calculate delay (uses server retry if available, else exponential backoff)
439+ delay = self ._get_next_reconnection_delay (attempt )
440+ logger .info (f"SSE stream closed, reconnecting in { delay :.1f} s (attempt { attempt + 1 } /{ max_retries } )" )
441+
442+ await anyio .sleep (delay )
443+
444+ # Build resumption context with last_event_id
445+ resumption_metadata = ClientMessageMetadata (
446+ resumption_token = last_event_id ,
447+ on_resumption_token_update = (ctx .metadata .on_resumption_token_update if ctx .metadata else None ),
448+ )
449+
450+ resumption_ctx = RequestContext (
451+ client = ctx .client ,
452+ headers = ctx .headers ,
453+ session_id = ctx .session_id ,
454+ session_message = ctx .session_message ,
455+ metadata = resumption_metadata ,
456+ read_stream_writer = ctx .read_stream_writer ,
457+ sse_read_timeout = ctx .sse_read_timeout ,
458+ )
459+
460+ try :
461+ await self ._handle_resumption_request (resumption_ctx )
462+ except Exception as e :
463+ logger .warning (f"Reconnection attempt { attempt + 1 } failed: { e } " )
464+ # Recursive retry with incremented attempt counter
465+ await self ._attempt_sse_reconnection (ctx , last_event_id , attempt + 1 )
412466
413467 async def _handle_unexpected_content_type (
414468 self ,
0 commit comments