agent_client_protocol/concepts/error_handling.rs
1//! Error handling patterns in agent-client-protocol.
2//!
3//! This chapter explains how errors work in agent-client-protocol callbacks and the difference
4//! between *protocol errors* (sent to the peer) and *connection errors* (which
5//! shut down the connection).
6//!
7//! # Callback Return Types
8//!
9//! Almost all agent-client-protocol callbacks return `Result<_, crate::Error>`.
10//! What happens when you return an `Err` depends on the context. An incoming
11//! request-handler error is sent to the peer as an Error Response, while an
12//! incoming notification-handler error is logged without a reply. Errors from
13//! connection-lifecycle callbacks can shut down the connection.
14//!
15//! # Sending Protocol Errors
16//!
17//! To choose an error response explicitly while handling a request, use the
18//! request context's `respond` method:
19//!
20//! ```
21//! # use agent_client_protocol::{Client, Agent, ConnectTo};
22//! # use agent_client_protocol_test::{ValidateRequest, ValidateResponse};
23//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
24//! Client.builder()
25//! .on_receive_request(async |request: ValidateRequest, responder, _cx| {
26//! if request.data.is_empty() {
27//! // Send error to peer, keep connection alive
28//! responder.respond_with_error(agent_client_protocol::Error::invalid_params())?;
29//! return Ok(());
30//! }
31//!
32//! // Process valid request...
33//! responder.respond(ValidateResponse { is_valid: true, error: None })?;
34//! Ok(())
35//! }, agent_client_protocol::on_receive_request!())
36//! # .connect_with(transport, async |_| Ok(())).await?;
37//! # Ok(())
38//! # }
39//! ```
40//!
41//! JSON-RPC notifications are one-way and cannot receive success or error replies.
42//! The SDK logs notification parse and handler errors without answering them. If an
43//! application needs to report a one-way failure, define a notification method for
44//! that purpose.
45//!
46//! Malformed JSON and invalid request envelopes are handled at the SDK's transport
47//! boundary. Applications do not need to construct uncorrelated Error Responses.
48//!
49//! # The `into_internal_error` Helper
50//!
51//! When working with external libraries that return their own error types,
52//! you need to convert them to `agent_client_protocol::Error`. The
53//! [`Error::into_internal_error`][crate::Error::into_internal_error] method
54//! provides a convenient way to do this:
55//!
56//! ```
57//! use agent_client_protocol::Error;
58//!
59//! # fn example() -> Result<(), agent_client_protocol::Error> {
60//! # let data = "hello";
61//! # let path = "/tmp/test.txt";
62//! // Convert any error type to agent_client_protocol::Error
63//! let value = serde_json::to_value(&data)
64//! .map_err(Error::into_internal_error)?;
65//!
66//! // Or with a file operation
67//! let contents = std::fs::read_to_string(path)
68//! .map_err(Error::into_internal_error);
69//! # Ok(())
70//! # }
71//! ```
72//!
73//! This wraps the original error's message in an internal error, which is
74//! appropriate for unexpected failures. For expected error conditions that
75//! should be communicated to the peer, create specific error types instead.
76//!
77//! # Error Types
78//!
79//! The [`Error`][crate::Error] type provides factory methods for common
80//! JSON-RPC error codes:
81//!
82//! - [`Error::parse_error()`][crate::Error::parse_error] - Invalid JSON
83//! - [`Error::invalid_request()`][crate::Error::invalid_request] - Malformed request
84//! - [`Error::method_not_found()`][crate::Error::method_not_found] - Unknown method
85//! - [`Error::invalid_params()`][crate::Error::invalid_params] - Bad parameters
86//! - [`Error::internal_error()`][crate::Error::internal_error] - Server error
87//!
88//! ACP also defines its own error codes, with matching factory methods:
89//!
90//! - [`Error::auth_required()`][crate::Error::auth_required] - `-32000`, the
91//! request needs authentication first
92//! - [`Error::resource_not_found()`][crate::Error::resource_not_found] -
93//! `-32002` for a missing resource such as a file, with an optional
94//! `data.uri`
95//! - [`Error::session_not_found()`][crate::Error::session_not_found] -
96//! `-32002` with `data.sessionId`, for a session-scoped request naming a
97//! session that does not exist or is not active on the connection
98//! - [`Error::request_cancelled()`][crate::Error::request_cancelled] -
99//! `-32800`, the request was cancelled
100//! - `schema::v2::Error::unsupported_protocol_version()` - `-32022`, the agent
101//! supports no protocol version at or below the requested one (protocol v2)
102//!
103//! See the protocol's error pages for when each code applies:
104//! [v1](https://agentclientprotocol.com/protocol/v1/error) and
105//! [v2](https://agentclientprotocol.com/protocol/v2/error).
106//!
107//! You can add context with `.data()`:
108//!
109//! ```
110//! let error = agent_client_protocol::Error::invalid_params()
111//! .data(serde_json::json!({
112//! "field": "timeout",
113//! "reason": "must be positive"
114//! }));
115//! ```
116//!
117//! # Summary
118//!
119//! | Situation | What to do |
120//! |-----------|------------|
121//! | Send error response to request | `responder.respond_with_error(error)` |
122//! | Handle notification failure | Log or return the error; the SDK sends no reply |
123//! | Fail connection lifecycle work | Return `Err(error)` from a lifecycle callback |
124//! | Convert external error | `.map_err(Error::into_internal_error)?` |