pub struct SentRequest<T> { /* private fields */ }Expand description
Represents a pending response of type R from an outgoing request.
Returned by ConnectionTo::send_request, this type provides methods for handling
the response without blocking the event loop. The API is intentionally designed to make
it difficult to accidentally block.
§Anti-Footgun Design
You cannot directly .await a SentRequest. Instead, you must choose how to handle
the response:
§Option 1: Schedule a Callback (Safe in Handlers)
Use on_receiving_result to schedule a task
that runs when the response arrives. This doesn’t block the event loop:
cx.send_request(MyRequest {})
.on_receiving_result(async |result| {
match result {
Ok(response) => {
// Handle successful response
Ok(())
}
Err(error) => {
// Handle error
Err(error)
}
}
})?;§Option 2: Block in a Spawned Task (Safe Only in spawn)
Use block_task to block until the response arrives, but only
in a spawned task (never in a handler):
// ✅ Safe: Spawned task runs concurrently
cx.spawn({
let cx = cx.clone();
async move {
let response = cx.send_request(MyRequest {})
.block_task()
.await?;
// Process response...
Ok(())
}
})?;// ❌ NEVER do this in a handler - blocks the event loop!
connection.on_receive_request(async |req: MyRequest, responder, cx| {
let response = cx.send_request(MyRequest {})
.block_task() // This will deadlock!
.await?;
responder.respond(response)
}, agent_client_protocol::on_receive_request!())§Why This Design?
If you block the event loop while waiting for a response, the connection cannot process the incoming response message, creating a deadlock. This API design prevents that footgun by making blocking explicit and encouraging non-blocking patterns.
Implementations§
Source§impl<T: JsonRpcResponse> SentRequest<T>
impl<T: JsonRpcResponse> SentRequest<T>
Sourcepub fn map<U>(
self,
map_fn: impl Fn(T) -> Result<U, Error> + 'static + Send,
) -> SentRequest<U>
pub fn map<U>( self, map_fn: impl Fn(T) -> Result<U, Error> + 'static + Send, ) -> SentRequest<U>
Create a new response that maps the result of the response to a new type.
Sourcepub fn forward_response_to(self, responder: Responder<T>) -> Result<(), Error>where
T: Send,
pub fn forward_response_to(self, responder: Responder<T>) -> Result<(), Error>where
T: Send,
Forward the response (success or error) to a request context when it arrives.
This is a convenience method for proxying messages between connections. When the response arrives, it will be automatically sent to the provided request context, whether it’s a successful response or an error.
§Example: Proxying requests
// Set up backend connection builder
let backend = UntypedRole.builder()
.on_receive_request(async |req: MyRequest, responder, cx| {
responder.respond(MyResponse { status: "ok".into() })
}, agent_client_protocol::on_receive_request!());
// Spawn backend and get a context to send to it
let backend_connection = cx.spawn_connection(backend, MockTransport)?;
// Set up proxy that forwards requests to backend
UntypedRole.builder()
.on_receive_request({
let backend_connection = backend_connection.clone();
async move |req: MyRequest, responder, cx| {
// Forward the request to backend and proxy the response back
backend_connection.send_request(req)
.forward_response_to(responder)?;
Ok(())
}
}, agent_client_protocol::on_receive_request!());§Type Safety
The request context’s response type must match the request’s response type, ensuring type-safe message forwarding.
§When to Use
Use this when:
- You’re implementing a proxy or gateway pattern
- You want to forward responses without processing them
- The response types match between the outgoing request and incoming request
This is equivalent to calling on_receiving_result and manually forwarding
the result, but more concise.
Sourcepub async fn block_task(self) -> Result<T, Error>where
T: Send,
pub async fn block_task(self) -> Result<T, Error>where
T: Send,
Block the current task until the response is received.
Warning: This method blocks the current async task. It is only safe to use
in spawned tasks created with ConnectionTo::spawn. Using it directly in a
handler callback will deadlock the connection.
§Safe Usage (in spawned tasks)
connection.on_receive_request(async |req: MyRequest, responder, cx| {
// Spawn a task to handle the request
cx.spawn({
let connection = cx.clone();
async move {
// Safe: We're in a spawned task, not blocking the event loop
let response = connection.send_request(OtherRequest {})
.block_task()
.await?;
// Process the response...
Ok(())
}
})?;
// Respond immediately
responder.respond(MyResponse { status: "ok".into() })
}, agent_client_protocol::on_receive_request!())§Unsafe Usage (in handlers - will deadlock!)
connection.on_receive_request(async |req: MyRequest, responder, cx| {
// ❌ DEADLOCK: Handler blocks event loop, which can't process the response
let response = cx.send_request(OtherRequest {})
.block_task()
.await?;
responder.respond(MyResponse { status: response.value })
}, agent_client_protocol::on_receive_request!())§When to Use
Use this method when:
- You’re in a spawned task (via
ConnectionTo::spawn) - You need the response value to proceed with your logic
- Linear control flow is more natural than callbacks
For handler callbacks, use on_receiving_result instead.
Sourcepub fn on_receiving_ok_result<F>(
self,
responder: Responder<T>,
task: impl FnOnce(T, Responder<T>) -> F + 'static + Send,
) -> Result<(), Error>
pub fn on_receiving_ok_result<F>( self, responder: Responder<T>, task: impl FnOnce(T, Responder<T>) -> F + 'static + Send, ) -> Result<(), Error>
Schedule an async task to run when a successful response is received.
This is a convenience wrapper around on_receiving_result
for the common pattern of forwarding errors to a request context while only processing
successful responses.
§Behavior
- If the response is
Ok(value), your task receives the value and the request context - If the response is
Err(error), the error is automatically sent toresponderand your task is not called
§Example: Chaining requests
connection.on_receive_request(async |req: ValidateRequest, responder, cx| {
// Send initial request
cx.send_request(ValidateRequest { data: req.data.clone() })
.on_receiving_ok_result(responder, async |validation, responder| {
// Only runs if validation succeeded
if validation.is_valid {
// Respond to original request
responder.respond(ValidateResponse { is_valid: true, error: None })
} else {
responder.respond_with_error(agent_client_protocol::util::internal_error("validation failed"))
}
})?;
Ok(())
}, agent_client_protocol::on_receive_request!())§Ordering
Like on_receiving_result, the callback blocks the
dispatch loop until it completes. See the ordering module
for details.
§When to Use
Use this when:
- You need to respond to a request based on another request’s result
- You want errors to automatically propagate to the request context
- You only care about the success case
For more control over error handling, use on_receiving_result.
Sourcepub fn on_receiving_result<F>(
self,
task: impl FnOnce(Result<T, Error>) -> F + 'static + Send,
) -> Result<(), Error>
pub fn on_receiving_result<F>( self, task: impl FnOnce(Result<T, Error>) -> F + 'static + Send, ) -> Result<(), Error>
Schedule an async task to run when the response is received.
This is the recommended way to handle responses in handler callbacks, as it doesn’t block the event loop. The task will be spawned automatically when the response arrives.
§Example: Handle response in callback
connection.on_receive_request(async |req: MyRequest, responder, cx| {
// Send a request and schedule a callback for the response
cx.send_request(QueryRequest { id: 22 })
.on_receiving_result({
let connection = cx.clone();
async move |result| {
match result {
Ok(response) => {
println!("Got response: {:?}", response);
// Can send more messages here
connection.send_notification(QueryComplete {})?;
Ok(())
}
Err(error) => {
eprintln!("Request failed: {}", error);
Err(error)
}
}
}
})?;
// Handler continues immediately without waiting
responder.respond(MyResponse { status: "processing".into() })
}, agent_client_protocol::on_receive_request!())§Ordering
The callback runs as a spawned task, but the dispatch loop waits for it to complete before processing the next message. This gives you ordering guarantees: no other messages will be processed while your callback runs.
This differs from block_task, which signals completion immediately
upon receiving the response (before your code processes it).
See the ordering module for details on ordering guarantees
and how to avoid deadlocks.
§Error Handling
If the scheduled task returns Err, the entire server will shut down. Make sure to handle
errors appropriately within your task.
§When to Use
Use this method when:
- You’re in a handler callback (not a spawned task)
- You want ordering guarantees (no other messages processed during your callback)
- You need to do async work before “releasing” control back to the dispatch loop
For spawned tasks where you don’t need ordering guarantees, consider block_task.