Skip to main content

SentRequest

Struct SentRequest 

Source
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>

Source

pub fn id(&self) -> Value

The id of the outgoing request.

Source

pub fn method(&self) -> &str

The method of the request this is in response to.

Source

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.

Source

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.

Source

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.

Source

pub fn on_receiving_ok_result<F>( self, responder: Responder<T>, task: impl FnOnce(T, Responder<T>) -> F + 'static + Send, ) -> Result<(), Error>
where F: Future<Output = Result<(), Error>> + 'static + Send, T: Send,

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 to responder and 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.

Source

pub fn on_receiving_result<F>( self, task: impl FnOnce(Result<T, Error>) -> F + 'static + Send, ) -> Result<(), Error>
where F: Future<Output = Result<(), Error>> + 'static + Send, T: Send,

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.

Trait Implementations§

Source§

impl<T: Debug> Debug for SentRequest<T>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T> Freeze for SentRequest<T>

§

impl<T> !RefUnwindSafe for SentRequest<T>

§

impl<T> Send for SentRequest<T>

§

impl<T> !Sync for SentRequest<T>

§

impl<T> Unpin for SentRequest<T>

§

impl<T> UnsafeUnpin for SentRequest<T>

§

impl<T> !UnwindSafe for SentRequest<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoMaybeUndefined<T> for T

Source§

impl<T> IntoOption<T> for T

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more