4313
4314
async fn handle_cancel(&self, args: CancelNotification) -> agent_client_protocol::Result<()> {
4315
log::info!("cancel requested for session {}", args.session_id);
4316
+ self.cancel_turn(&args.session_id);
4317
+ Ok(())
4318
+ }
4319
+
4320
+ /// Stop the turn running on `session_id`, if there is one, and whatever
4321
+ /// command it is running. Reached from `session/cancel` and from a
4322
+ /// `$/cancel_request` naming the prompt.
4323
+ fn cancel_turn(&self, session_id: &SessionId) {
4324
if let Some(cancellation) = self
4325
.prompt_cancellations
4326
.lock()
4327
.unwrap()
4320
- .get(&args.session_id.to_string())
4328
+ .get(&session_id.to_string())
4329
.cloned()
4330
{
4331
cancellation.cancelled.store(true, Ordering::Release);
4332
cancellation.notify.notify_one();
4333
}
4326
- tools::abort_session_tools(&args.session_id.to_string());
4327
- Ok(())
4334
+ tools::abort_session_tools(&session_id.to_string());
4335
}
4336
4337
/// Swap the active backend to a siGit Code Cloud tier and reflect it as the
6193
let state = Arc::clone(&state);
6194
let task_cx = cx.clone();
6195
let request_id = responder.id().clone();
6196
+ // A sign-in waits on the browser for minutes. Dropping it
6197
+ // on `$/cancel_request` closes the loopback listener, and
6198
+ // the request is answered `-32800`.
6199
+ let cancellation = responder.cancellation();
6200
cx.spawn(async move {
6190
- handle_response(
6191
- responder,
6192
- state.handle_authenticate(&task_cx, request_id, req).await,
6193
- )
6201
+ let result = cancellation
6202
+ .run_until_cancelled(state.handle_authenticate(
6203
+ &task_cx, request_id, req,
6204
+ ))
6205
+ .await;
6206
+ handle_response(responder, result)
6207
})
6208
}
6209
},
6340
async move |req: PromptRequest, responder, cx: ConnectionTo<Client>| {
6341
let state = Arc::clone(&state);
6342
let task_cx = cx.clone();
6343
+ let cancellation = responder.cancellation();
6344
cx.spawn(async move {
6345
let _session = state.lock_session(&req.session_id).await;
6332
- handle_response(responder, state.handle_prompt(&task_cx, req).await)
6346
+ let session_id = req.session_id.clone();
6347
+ let turn = state.handle_prompt(&task_cx, req);
6348
+ tokio::pin!(turn);
6349
+ // `$/cancel_request` for the prompt's own id stops the
6350
+ // turn the way `session/cancel` does, and the request
6351
+ // is still answered with a `cancelled` stop reason so
6352
+ // the client keeps what was streamed. The turn is
6353
+ // polled first: it registers for cancellation on its
6354
+ // first poll, which a cancel that arrived while the
6355
+ // prompt was queued would otherwise beat.
6356
+ let result = tokio::select! {
6357
+ biased;
6358
+ result = &mut turn => result,
6359
+ () = cancellation.cancelled() => {
6360
+ log::info!("$/cancel_request for the prompt on session {session_id}");
6361
+ state.cancel_turn(&session_id);
6362
+ turn.await
6363
+ }
6364
+ };
6365
+ handle_response(responder, result)
6366
})
6367
}
6368
},