@@ -209,12 +209,13 @@ async def call_tool(self, tool_name, arguments, meta=None):
209209
210210
211211class TimeoutSession :
212- def __init__ (self ):
212+ def __init__ (self , message : str = "timed out" ):
213213 self .call_tool_attempts = 0
214+ self .message = message
214215
215216 async def call_tool (self , tool_name , arguments , meta = None ):
216217 self .call_tool_attempts += 1
217- raise httpx .TimeoutException ("timed out" )
218+ raise httpx .TimeoutException (self . message )
218219
219220
220221class IsolatedRetrySession :
@@ -255,6 +256,29 @@ async def get_prompt(self, name, arguments=None):
255256 raise NotImplementedError
256257
257258
259+ class IsolatedSessionEnterFailure :
260+ def __init__ (self , server : "EnterFailingStreamableHttpServer" , message : str ):
261+ self .server = server
262+ self .message = message
263+
264+ async def __aenter__ (self ):
265+ self .server .isolated_enter_attempts += 1
266+ raise httpx .TimeoutException (self .message )
267+
268+ async def __aexit__ (self , exc_type , exc , tb ):
269+ return False
270+
271+
272+ class EnterFailingStreamableHttpServer (DummyStreamableHttpServer ):
273+ def __init__ (self , shared_session : object , * , isolated_message : str ):
274+ super ().__init__ (shared_session , IsolatedRetrySession ())
275+ self .isolated_enter_attempts = 0
276+ self ._isolated_message = isolated_message
277+
278+ def _isolated_client_session (self ):
279+ return IsolatedSessionEnterFailure (self , self ._isolated_message )
280+
281+
258282@pytest .mark .asyncio
259283async def test_streamable_http_retries_cancelled_request_on_isolated_session ():
260284 shared_session = CancelledToolSession ()
@@ -305,18 +329,34 @@ async def test_streamable_http_does_not_isolated_retry_without_retry_budget():
305329
306330@pytest .mark .asyncio
307331async def test_streamable_http_counts_isolated_retry_against_retry_budget ():
308- shared_session = TimeoutSession ()
309- isolated_session = TimeoutSession ()
332+ shared_session = TimeoutSession ("shared timed out" )
333+ isolated_session = TimeoutSession ("isolated timed out" )
310334 server = DummyStreamableHttpServer (shared_session , isolated_session )
311335 server .max_retry_attempts = 2
312336
313- with pytest .raises (httpx .TimeoutException , match = "timed out" ):
337+ with pytest .raises (httpx .TimeoutException , match = "shared timed out" ):
314338 await server .call_tool ("tool" , None )
315339
316340 assert shared_session .call_tool_attempts == 2
317341 assert isolated_session .call_tool_attempts == 1
318342
319343
344+ @pytest .mark .asyncio
345+ async def test_streamable_http_counts_isolated_session_setup_failure_against_retry_budget ():
346+ shared_session = TimeoutSession ("shared timed out" )
347+ server = EnterFailingStreamableHttpServer (
348+ shared_session ,
349+ isolated_message = "isolated setup timed out" ,
350+ )
351+ server .max_retry_attempts = 2
352+
353+ with pytest .raises (httpx .TimeoutException , match = "shared timed out" ):
354+ await server .call_tool ("tool" , None )
355+
356+ assert shared_session .call_tool_attempts == 2
357+ assert server .isolated_enter_attempts == 1
358+
359+
320360@pytest .mark .asyncio
321361async def test_streamable_http_does_not_retry_mixed_exception_groups ():
322362 isolated_session = IsolatedRetrySession ()
0 commit comments