Skip to main content

agent_client_protocol/concepts/
callbacks.rs

1//! Handling incoming messages with `on_receive_*` callbacks.
2//!
3//! So far we've seen how to *send* messages. But ACP is bidirectional - the
4//! remote peer can also send messages to you. Use callbacks to handle them.
5//!
6//! # Handling Requests
7//!
8//! Use `on_receive_request` to handle incoming requests that expect a response:
9//!
10//! ```
11//! # use agent_client_protocol::{Client, Agent, ConnectTo};
12//! # use agent_client_protocol_test::{ValidateRequest, ValidateResponse};
13//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
14//! Client.builder()
15//!     .on_receive_request(async |req: ValidateRequest, responder, cx| {
16//!         // Process the request
17//!         let is_valid = req.data.len() > 0;
18//!
19//!         // Send the response
20//!         responder.respond(ValidateResponse { is_valid, error: None })
21//!     }, agent_client_protocol::on_receive_request!())
22//!     .connect_with(transport, async |cx| { Ok(()) })
23//!     .await?;
24//! # Ok(())
25//! # }
26//! ```
27//!
28//! Your callback receives three arguments:
29//! - The request payload (e.g., `PermissionRequest`)
30//! - A [`Responder`] for sending the response
31//! - A [`ConnectionTo`] for sending other messages
32//!
33//! # Handling Notifications
34//!
35//! Use `on_receive_notification` for fire-and-forget messages that don't need
36//! a response:
37//!
38//! ```
39//! # use agent_client_protocol::{Client, Agent, ConnectTo};
40//! # use agent_client_protocol_test::StatusUpdate;
41//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
42//! Client.builder()
43//!     .on_receive_notification(async |notif: StatusUpdate, cx| {
44//!         println!("Status: {}", notif.message);
45//!         Ok(())
46//!     }, agent_client_protocol::on_receive_notification!())
47//! #   .connect_with(transport, async |_| Ok(())).await?;
48//! # Ok(())
49//! # }
50//! ```
51//!
52//! # The Request Context
53//!
54//! The [`Responder`] lets you send a response to the request:
55//!
56//! ```
57//! # use agent_client_protocol::{Client, Agent, ConnectTo};
58//! # use agent_client_protocol_test::{MyRequest, MyResponse};
59//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
60//! # Client.builder()
61//! #   .on_receive_request(async |req: MyRequest, responder, cx| {
62//! // Send a successful response
63//! responder.respond(MyResponse { status: "ok".into() })?;
64//! # Ok(())
65//! #   }, agent_client_protocol::on_receive_request!())
66//! #   .connect_with(transport, async |_| Ok(())).await?;
67//! # Ok(())
68//! # }
69//! ```
70//!
71//! Or send an error:
72//!
73//! ```
74//! # use agent_client_protocol::{Client, Agent, ConnectTo};
75//! # use agent_client_protocol_test::{MyRequest, MyResponse};
76//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
77//! # Client.builder()
78//! #   .on_receive_request(async |req: MyRequest, responder, cx| {
79//! responder.respond_with_error(agent_client_protocol::Error::invalid_params())?;
80//! # Ok(())
81//! #   }, agent_client_protocol::on_receive_request!())
82//! #   .connect_with(transport, async |_| Ok(())).await?;
83//! # Ok(())
84//! # }
85//! ```
86//!
87//! Send at most one response per request. If a handler responds and then
88//! returns an error, the first response wins and the duplicate completion is
89//! ignored. Dropping a responder from a batch produces an automatic Internal
90//! Error so sibling responses are not stranded; dropping the responder for an
91//! individual request sends no response.
92//!
93//! # Multiple Handlers
94//!
95//! You can register multiple handlers. They're tried in order until one
96//! handles the message:
97//!
98//! ```
99//! # use agent_client_protocol::{Client, Agent, ConnectTo};
100//! # use agent_client_protocol_test::{ValidateRequest, ValidateResponse, ExecuteRequest, ExecuteResponse};
101//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
102//! Client.builder()
103//!     .on_receive_request(async |req: ValidateRequest, responder, cx| {
104//!         // Handle validation requests
105//!         responder.respond(ValidateResponse { is_valid: true, error: None })
106//!     }, agent_client_protocol::on_receive_request!())
107//!     .on_receive_request(async |req: ExecuteRequest, responder, cx| {
108//!         // Handle execution requests
109//!         responder.respond(ExecuteResponse { result: "done".into() })
110//!     }, agent_client_protocol::on_receive_request!())
111//! #   .connect_with(transport, async |_| Ok(())).await?;
112//! # Ok(())
113//! # }
114//! ```
115//!
116//! # Ordering Guarantees
117//!
118//! Callbacks run inside the dispatch loop and block further message processing
119//! until they complete. This gives you ordering guarantees but also means you
120//! need to be careful about deadlocks.
121//!
122//! See [Ordering](super::ordering) for the full details.
123//!
124//! # Next Steps
125//!
126//! - [Explicit Peers](super::peers) - Use `_from` variants to specify the source peer
127//! - [Ordering](super::ordering) - Understand dispatch loop semantics
128//!
129//! [`Responder`]: crate::Responder
130//! [`ConnectionTo`]: crate::ConnectionTo