#[non_exhaustive]pub enum ClientError {
Http(Error),
HttpClient(String),
Serialization(Error),
Protocol(A2aError),
Transport(String),
InvalidEndpoint(String),
UnexpectedStatus {
status: u16,
body: String,
retry_after: Option<Duration>,
},
AuthRequired {
task_id: TaskId,
},
Timeout(String),
TooManyPendingRequests {
limit: usize,
},
IncompleteStream {
last_event_id: Option<String>,
detail: String,
},
ProtocolBindingMismatch(String),
}Expand description
Errors that can occur during A2A client operations.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Http(Error)
A transport-level HTTP error from hyper.
HttpClient(String)
An HTTP-level error from the hyper-util client (connection, redirect, etc.).
Serialization(Error)
JSON serialization or deserialization error.
Protocol(A2aError)
A protocol-level A2A error returned by the server.
Transport(String)
A transport configuration or connection error.
InvalidEndpoint(String)
The agent endpoint URL is invalid or could not be resolved.
UnexpectedStatus
The server returned an unexpected HTTP status code.
Fields
AuthRequired
The agent requires authentication for this task.
Timeout(String)
A request or stream connection timed out.
TooManyPendingRequests
The connection already has limit requests awaiting responses, and
this one was refused up front rather than queued. Retryable: room
appears as responses arrive. See
WebSocketTransportConfig::max_pending_requests.
IncompleteStream
A stream ended before the event that finishes it.
A stream finishes cleanly on a Message, or on a Task or status
update whose state is terminal (completed, failed, canceled,
rejected) or interrupted (input-required, auth-required) — the
states at which the specification says the server closes it (§3.1.2,
§11.7). A body that ends, or a connection that closes, anywhere else
is this error rather than a quiet None: the task is very likely still
running, and the consumer is missing the rest of it.
Retryable in the sense that matters for a stream: resubscribe with
A2aClient::subscribe_to_task_from,
passing last_event_id, and a server that keeps an event log replays
what was missed.
Fields
ProtocolBindingMismatch(String)
The server appears to use a different protocol binding than the client.
For example, a JSON-RPC client connected to a REST-only server (or
vice-versa). Check the agent card’s supported_interfaces to select
the correct protocol binding.
Implementations§
Source§impl ClientError
impl ClientError
Sourcepub const fn retry_after(&self) -> Option<Duration>
pub const fn retry_after(&self) -> Option<Duration>
Server-requested retry delay, if this error carries one (a Retry-After
header on a 429/503). The retry layer prefers this over its computed
backoff.
Sourcepub fn is_stream_lagged(&self) -> bool
pub fn is_stream_lagged(&self) -> bool
Returns true when this is the consumer-lag signal on a streaming
subscription: the reader fell too far behind the agent, and the stream
was cut off, rather than the task failing.
The stream ends here; the task does not. This SDK’s server writes
one lag error frame when a reader overruns its event queue and then
closes the stream, and the WebSocket transport ends a stream the same
way once 64 frames are waiting unread (audit N29). What was read is a
contiguous prefix. To continue, resubscribe: over SSE with
subscribe_to_task_from
and the stream’s last event id, which replays what was missed.
while let Some(event) = stream.next().await {
match event {
Ok(ev) => { /* handle */ }
// Not a task failure: the stream was cut off. Resubscribe.
Err(e) if e.is_stream_lagged() => {
eprintln!("stream cut off after {:?} dropped events", e.dropped_event_count());
break;
}
Err(e) => break, // a real failure
}
}Sourcepub fn dropped_event_count(&self) -> Option<u64>
pub fn dropped_event_count(&self) -> Option<u64>
Number of events the server dropped, when this is a consumer-lag
signal (see ClientError::is_stream_lagged); None otherwise.
Source§impl ClientError
impl ClientError
Sourcepub const fn is_retryable(&self) -> bool
pub const fn is_retryable(&self) -> bool
Returns true if this error is transient and the request should be retried.
Retryable errors include:
- HTTP connection/transport errors
- Timeouts
- Server errors (HTTP 502, 503, 504, 429)
- The client’s own in-flight cap (
TooManyPendingRequests), which clears as responses arrive - A stream that ended before its final event (
IncompleteStream), which a resubscribe resumes
Trait Implementations§
Source§impl Debug for ClientError
impl Debug for ClientError
Source§impl Display for ClientError
impl Display for ClientError
Source§impl Error for ClientError
impl Error for ClientError
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()
Source§impl From<A2aError> for ClientError
impl From<A2aError> for ClientError
Source§fn from(e: A2aError) -> ClientError
fn from(e: A2aError) -> ClientError
Source§impl From<ClientError> for A2aError
Lets an executor that delegates to another agent use ? on client calls.
impl From<ClientError> for A2aError
Lets an executor that delegates to another agent use ? on client calls.
An AgentExecutor returns A2aResult, so without this every delegated
call had to be flattened to a string, losing whether it was worth
retrying. The conversion keeps what the server needs to classify the
failed task (see a2a_protocol_types::failure::error_class):
ClientError | A2aError code | Failure class |
|---|---|---|
Protocol(e) | e.code, with e.message and e.data unchanged | from the code |
anything is_retryable — timeouts, connection failures, 429/502/503/504, TooManyPendingRequests | InternalError | Transient |
Serialization (the peer sent something unreadable) | InvalidAgentResponse | Internal |
| everything else (bad endpoint, binding mismatch, other statuses, auth required) | InternalError | Internal |
“Transient exactly when the client would retry” is deliberate: the
client’s retry policy and the caller’s are then the same judgement.
The class rides in data under the failure key; the server never sends
data to its caller as-is, so it does not leak. The message is
downstream A2A call failed: followed by this error’s text and its
source chain, so nothing the client knew is lost.
use a2a_protocol_client::ClientBuilder;
use a2a_protocol_types::error::A2aResult;
use a2a_protocol_types::params::TaskQueryParams;
// The shape of an `AgentExecutor::execute` body that delegates.
async fn delegate(downstream: &str, task_id: &str) -> A2aResult<String> {
let client = ClientBuilder::new(downstream).build()?;
let task = client
.get_task(TaskQueryParams {
tenant: None,
id: task_id.to_owned(),
history_length: None,
})
.await?; // a timeout here fails the task as `Transient`
Ok(task.text().unwrap_or_default().to_owned())
}Source§fn from(e: ClientError) -> A2aError
fn from(e: ClientError) -> A2aError
Source§impl From<Error> for ClientError
impl From<Error> for ClientError
Source§fn from(e: Error) -> ClientError
fn from(e: Error) -> ClientError
Source§impl From<Error> for ClientError
impl From<Error> for ClientError
Source§fn from(e: Error) -> ClientError
fn from(e: Error) -> ClientError
Auto Trait Implementations§
impl !RefUnwindSafe for ClientError
impl !UnwindSafe for ClientError
impl Freeze for ClientError
impl Send for ClientError
impl Sync for ClientError
impl Unpin for ClientError
impl UnsafeUnpin for ClientError
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
Source§fn with_current_context(self) -> WithContext<Self> ⓘ
fn with_current_context(self) -> WithContext<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request