|
| 1 | +# Request Cancellation |
| 2 | + |
| 3 | +The SDK exposes the ACP `$/cancel_request` notification behind the |
| 4 | +`unstable_cancel_request` feature. The notification is protocol-level: either |
| 5 | +side may send it to ask the peer to cancel one outstanding JSON-RPC request by |
| 6 | +ID. |
| 7 | + |
| 8 | +Enable the feature when depending on the crate: |
| 9 | + |
| 10 | +```toml |
| 11 | +agent-client-protocol = { version = "...", features = ["unstable_cancel_request"] } |
| 12 | +``` |
| 13 | + |
| 14 | +To cancel a request sent through `ConnectionTo::send_request`, keep the |
| 15 | +returned `SentRequest` and call `cancel` on it: |
| 16 | + |
| 17 | +```rust |
| 18 | +# use agent_client_protocol::{ConnectionTo, Error, UntypedRole}; |
| 19 | +# use agent_client_protocol_test::MyRequest; |
| 20 | +# async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), Error> { |
| 21 | +let request = cx.send_request(MyRequest {}); |
| 22 | +request.cancel()?; |
| 23 | +# Ok(()) |
| 24 | +# } |
| 25 | +``` |
| 26 | + |
| 27 | +The `SentRequest` remembers the peer and any proxy wrapping used for the |
| 28 | +original request, so this also works for requests sent through |
| 29 | +`ConnectionTo::send_request_to`. |
| 30 | + |
| 31 | +If you already have the JSON-RPC request ID, send the notification directly: |
| 32 | + |
| 33 | +```rust |
| 34 | +# use agent_client_protocol::{ConnectionTo, Error, UntypedRole}; |
| 35 | +# async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), Error> { |
| 36 | +cx.send_cancel_request("request-id".to_string())?; |
| 37 | +# Ok(()) |
| 38 | +# } |
| 39 | +``` |
| 40 | + |
| 41 | +For incoming requests, get the request-local cancellation marker from the |
| 42 | +`Responder`. This keeps cancellation handling next to the request work it |
| 43 | +controls: |
| 44 | + |
| 45 | +```rust |
| 46 | +# use agent_client_protocol::{ConnectionTo, Error, Responder, UntypedRole}; |
| 47 | +# use agent_client_protocol_test::{MyRequest, MyResponse}; |
| 48 | +# async fn example(request: MyRequest, responder: Responder<MyResponse>, cx: ConnectionTo<UntypedRole>) -> Result<(), Error> { |
| 49 | +# async fn run_request(_request: MyRequest) -> Result<MyResponse, Error> { todo!() } |
| 50 | +let cancellation = responder.cancellation(); |
| 51 | + |
| 52 | +cx.spawn(async move { |
| 53 | + let response = cancellation.run_until_cancelled(run_request(request)).await; |
| 54 | + responder.respond_with_result(response) |
| 55 | +})?; |
| 56 | +Ok(()) |
| 57 | +# } |
| 58 | +``` |
| 59 | + |
| 60 | +`run_until_cancelled` is the simple path for handlers that should stop work and |
| 61 | +reply with the standard cancellation error as soon as cancellation is requested. |
| 62 | +If the handler needs cleanup, partial results, or custom cancellation behavior, |
| 63 | +use `cancellation.cancelled()` or `cancellation.is_cancelled()` directly inside |
| 64 | +the request work instead. |
| 65 | + |
| 66 | +Cancellation markers are only updated when the connection can process the |
| 67 | +incoming `$/cancel_request` notification. Long-running handlers should return |
| 68 | +quickly and move work into `ConnectionTo::spawn`, `SentRequest` callbacks, or |
| 69 | +another task. |
| 70 | + |
| 71 | +When proxying with `SentRequest::forward_response_to`, the SDK observes the |
| 72 | +upstream `Responder` cancellation marker and forwards cancellation to the |
| 73 | +downstream request automatically. |
| 74 | + |
| 75 | +Register `CancelRequestNotification` or `ProtocolLevelNotification` directly |
| 76 | +only when you need low-level access to cancellation notifications, such as |
| 77 | +custom routing or protocol tracing: |
| 78 | + |
| 79 | +```rust |
| 80 | +# use agent_client_protocol::{ConnectionTo, Error, UntypedRole}; |
| 81 | +use agent_client_protocol::schema::CancelRequestNotification; |
| 82 | + |
| 83 | +# fn builder() -> agent_client_protocol::Builder<UntypedRole> { |
| 84 | +UntypedRole.builder() |
| 85 | + .on_receive_notification( |
| 86 | + async |cancel: CancelRequestNotification, _cx: ConnectionTo<UntypedRole>| { |
| 87 | + let request_id = cancel.request_id; |
| 88 | + // Mark the matching in-flight operation cancelled. |
| 89 | + Ok(()) |
| 90 | + }, |
| 91 | + agent_client_protocol::on_receive_notification!(), |
| 92 | + ) |
| 93 | +# } |
| 94 | +``` |
| 95 | + |
| 96 | +Cancellation is cooperative. A peer may ignore `$/cancel_request`, may finish |
| 97 | +with normal data, or may respond to the original request with |
| 98 | +`Error::request_cancelled()` (`-32800`). The SDK ignores unhandled `$/...` |
| 99 | +notifications so unsupported protocol-level notifications do not produce |
| 100 | +method-not-found errors. |
0 commit comments