@@ -77,6 +77,11 @@ def __init__(
7777 ) -> None :
7878 self ._handler = handler
7979 self ._next_request_id = 0
80+ # Track the notification interval for each outgoing request so its
81+ # response cannot overtake notifications received during that request.
82+ self ._notification_sequence = 0
83+ self ._pending_notifications : dict [int , asyncio .Future [None ]] = {}
84+ self ._request_notification_starts : dict [int , int ] = {}
8085 self ._state = state_store or InMemoryMessageStateStore ()
8186 self ._tasks = TaskSupervisor (source = "acp.Connection" )
8287 self ._tasks .add_error_handler (self ._on_task_error )
@@ -145,6 +150,7 @@ async def send_request(self, method: str, params: JsonValue | None = None) -> An
145150 self ._raise_if_unavailable ()
146151 request_id = self ._next_request_id
147152 self ._next_request_id += 1
153+ self ._request_notification_starts [request_id ] = self ._notification_sequence
148154 future = self ._state .register_outgoing (request_id , method )
149155 payload = {"jsonrpc" : "2.0" , "id" : request_id , "method" : method , "params" : params }
150156 try :
@@ -153,10 +159,14 @@ async def send_request(self, method: str, params: JsonValue | None = None) -> An
153159 # A synchronous send failure (e.g. HTTP POST rejected before any
154160 # JSON-RPC response exists) must reject the correlated future so the
155161 # caller gets a real, attributable error.
162+ self ._request_notification_starts .pop (request_id , None )
156163 self ._state .reject_outgoing (request_id , exc )
157164 raise
158165 self ._notify_observers (StreamDirection .OUTGOING , payload )
159- return await future
166+ try :
167+ return await future
168+ finally :
169+ self ._request_notification_starts .pop (request_id , None )
160170
161171 async def send_notification (self , method : str , params : JsonValue | None = None ) -> None :
162172 self ._raise_if_unavailable ()
@@ -185,10 +195,38 @@ async def _process_message(self, message: dict[str, Any]) -> None:
185195 await self ._queue .publish (RpcTask (RpcTaskKind .REQUEST , message ))
186196 return
187197 if method is not None and not has_id :
188- await self ._queue .publish (RpcTask (RpcTaskKind .NOTIFICATION , message ))
198+ self ._notification_sequence += 1
199+ sequence = self ._notification_sequence
200+ completion = asyncio .get_running_loop ().create_future ()
201+ self ._pending_notifications [sequence ] = completion
202+ completion .add_done_callback (lambda _ : self ._pending_notifications .pop (sequence , None ))
203+ await self ._queue .publish (RpcTask (RpcTaskKind .NOTIFICATION , message , completion ))
189204 return
190205 if has_id :
191- await self ._handle_response (message )
206+ request_id = message ["id" ]
207+ # Excluding notifications received before this request began keeps
208+ # notification handlers free to make nested requests without those
209+ # responses waiting on the handler that issued them.
210+ start_sequence = self ._request_notification_starts .get (request_id , self ._notification_sequence )
211+ preceding_notifications = tuple (
212+ completion for sequence , completion in self ._pending_notifications .items () if sequence > start_sequence
213+ )
214+ if preceding_notifications :
215+ self ._tasks .create (
216+ self ._handle_response_after_notifications (message , preceding_notifications ),
217+ name = "acp.Connection.response" ,
218+ on_error = self ._on_receive_error ,
219+ )
220+ else :
221+ await self ._handle_response (message )
222+
223+ async def _handle_response_after_notifications (
224+ self ,
225+ message : dict [str , Any ],
226+ preceding_notifications : tuple [asyncio .Future [None ], ...],
227+ ) -> None :
228+ await asyncio .gather (* (asyncio .shield (completion ) for completion in preceding_notifications ))
229+ await self ._handle_response (message )
192230
193231 def _notify_observers (self , direction : StreamDirection , message : dict [str , Any ]) -> None :
194232 if not self ._observers :
0 commit comments