@@ -85,6 +85,28 @@ def _lines():
8585 assert partial .tool_call_events [0 ].function_name == "create_key_driver_analysis"
8686
8787
88+ def test_parse_sse_lines_remote_protocol_error_mid_stream_is_retryable ():
89+ # httpx.RemoteProtocolError raised from `next(it)` (mid-stream, not at connect time) must
90+ # come out as TransientChatError -- otherwise _is_retryable_exc never sees the raw
91+ # RemoteProtocolError (only the ChatError parse_sse_lines wraps it in) and the retry this
92+ # class exists for never fires. Same partial_result guarantee as any other transport error.
93+ def _lines ():
94+ yield (
95+ 'data: {"item": {"role": "assistant", "content": '
96+ + json .dumps ({"type" : "toolCall" , "callId" : "c1" , "name" : "create_key_driver_analysis" })
97+ + "}}"
98+ )
99+ yield ""
100+ raise httpx .RemoteProtocolError ("peer closed connection without sending complete message body" )
101+
102+ with pytest .raises (TransientChatError ) as ei :
103+ parse_sse_lines (_lines ())
104+ partial = ei .value .partial_result
105+ assert partial is not None
106+ assert len (partial .tool_call_events ) == 1
107+ assert partial .tool_call_events [0 ].function_name == "create_key_driver_analysis"
108+
109+
88110def test_parse_sse_lines_a_real_parsing_bug_propagates_uncaught_not_as_a_chat_error ():
89111 # A malformed payload (here: "item" is a string, not a dict) crashes the processing
90112 # code itself with a plain AttributeError -- must surface loudly as that bug, not get
@@ -380,6 +402,26 @@ def handler(request):
380402 assert sleeps == [5 , 10 ]
381403
382404
405+ def test_create_conversation_retries_remote_protocol_error (monkeypatch ):
406+ # "peer closed connection without sending complete message body" -- a pure
407+ # network flake, not a real agent/content failure. Previously not retried
408+ # at all: hard-failed on the first occurrence with zero retry attempts.
409+ sleeps = []
410+ monkeypatch .setattr (sse_mod .time , "sleep" , lambda s : sleeps .append (s ))
411+ calls = {"n" : 0 }
412+
413+ def handler (request ):
414+ calls ["n" ] += 1
415+ if calls ["n" ] < 3 :
416+ raise httpx .RemoteProtocolError ("peer closed connection without sending complete message body" )
417+ return httpx .Response (200 , json = {"conversationId" : "abc" })
418+
419+ client = _client_with_handler (handler )
420+ assert client .create_conversation () == "abc"
421+ assert calls ["n" ] == 3
422+ assert sleeps == [5 , 10 ]
423+
424+
383425def test_create_conversation_does_not_retry_4xx (monkeypatch ):
384426 sleeps = []
385427 monkeypatch .setattr (sse_mod .time , "sleep" , lambda s : sleeps .append (s ))
0 commit comments