@@ -118,11 +118,14 @@ use tracing::info;
118118use tracing:: warn;
119119use uuid:: Uuid ;
120120
121+ use crate :: cancellation_registry:: CancellationRegistry ;
122+
121123// Duration before a ChatGPT login attempt is abandoned.
122124const LOGIN_CHATGPT_TIMEOUT : Duration = Duration :: from_secs ( 10 * 60 ) ;
123125struct ActiveLogin {
124126 shutdown_handle : ShutdownHandle ,
125127 login_id : Uuid ,
128+ request_id : RequestId ,
126129}
127130
128131impl ActiveLogin {
@@ -144,6 +147,7 @@ pub(crate) struct CodexMessageProcessor {
144147 pending_interrupts : Arc < Mutex < HashMap < ConversationId , Vec < RequestId > > > > ,
145148 pending_fuzzy_searches : Arc < Mutex < HashMap < String , Arc < AtomicBool > > > > ,
146149 feedback : CodexFeedback ,
150+ cancellation_registry : CancellationRegistry ,
147151}
148152
149153impl CodexMessageProcessor {
@@ -166,6 +170,7 @@ impl CodexMessageProcessor {
166170 pending_interrupts : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
167171 pending_fuzzy_searches : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
168172 feedback,
173+ cancellation_registry : CancellationRegistry :: default ( ) ,
169174 }
170175 }
171176
@@ -391,13 +396,22 @@ impl CodexMessageProcessor {
391396 let mut guard = self . active_login . lock ( ) . await ;
392397 if let Some ( existing) = guard. take ( ) {
393398 existing. drop ( ) ;
399+ self . cancellation_registry . remove ( & existing. request_id ) ;
394400 }
395401 * guard = Some ( ActiveLogin {
396402 shutdown_handle : shutdown_handle. clone ( ) ,
397403 login_id,
404+ request_id : request_id. clone ( ) ,
398405 } ) ;
399406 }
400407
408+ // Register cancellation for this request id so $/cancelRequest works.
409+ let shutdown_for_cancel = shutdown_handle. clone ( ) ;
410+ self . cancellation_registry
411+ . insert ( request_id. clone ( ) , move || {
412+ shutdown_for_cancel. shutdown ( ) ;
413+ } ) ;
414+
401415 let response = LoginChatGptResponse {
402416 login_id,
403417 auth_url : server. auth_url . clone ( ) ,
@@ -407,6 +421,8 @@ impl CodexMessageProcessor {
407421 let outgoing_clone = self . outgoing . clone ( ) ;
408422 let active_login = self . active_login . clone ( ) ;
409423 let auth_manager = self . auth_manager . clone ( ) ;
424+ let cancellation_registry = self . cancellation_registry . clone ( ) ;
425+ let request_id_for_task = request_id. clone ( ) ;
410426 tokio:: spawn ( async move {
411427 let ( success, error_msg) = match tokio:: time:: timeout (
412428 LOGIN_CHATGPT_TIMEOUT ,
@@ -451,6 +467,8 @@ impl CodexMessageProcessor {
451467 if guard. as_ref ( ) . map ( |l| l. login_id ) == Some ( login_id) {
452468 * guard = None ;
453469 }
470+
471+ cancellation_registry. remove ( & request_id_for_task) ;
454472 } ) ;
455473
456474 LoginChatGptReply :: Response ( response)
@@ -470,9 +488,22 @@ impl CodexMessageProcessor {
470488 }
471489 }
472490
473- /// Handle a generic JSON-RPC cancellation for a previously started operation.
474- /// This is a fire-and-forget path; no response.
475- pub async fn cancel_request ( & self , id : RequestId ) { }
491+ /// Handle a generic JSON-RPC `$ /cancelRequest` for a previously started operation.
492+ ///
493+ /// Note: individual request handlers that wish to be cancellable must
494+ /// register a cancellation action in the `CancellationRegistry` using the
495+ /// original JSON-RPC `request_id` when they start work. This method looks up
496+ /// that action by id and triggers it if found. It is fire-and-forget; no
497+ /// JSON-RPC response is sent.
498+ pub async fn cancel_request ( & self , id : RequestId ) {
499+ let found = self . cancellation_registry . cancel ( & id) ;
500+ if !found {
501+ tracing:: debug!(
502+ "$/cancelRequest for unknown or already-finished id: {:?}" ,
503+ id
504+ ) ;
505+ }
506+ }
476507
477508 // Legacy endpoint for cancelling a LoginChatGpt request. Please use $/cancelRequest instead.
478509 async fn cancel_login_chatgpt ( & mut self , request_id : RequestId , login_id : Uuid ) {
0 commit comments