use std::time::Duration;
use a2a_lf::error_code;
use super::config::{A2aConfigError, A2aUrlError};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum A2aError {
#[error("invalid A2A agent configuration: {0}")]
Config(#[from] A2aConfigError),
#[error("could not connect to the A2A agent")]
Connect,
#[error("the A2A agent exchange failed: {0}")]
Transport(&'static str),
#[error("the A2A agent answered {operation} with HTTP {status}")]
HttpStatus {
operation: &'static str,
status: reqwest::StatusCode,
},
#[error(
"the A2A agent answered with a redirect, which is not followed because it would send \
credentials to an unvalidated origin"
)]
RedirectRefused,
#[error("the A2A agent's reply exceeded the {limit} byte limit")]
ResponseTooLarge { limit: usize },
#[error("timed out after {0:?} waiting for the A2A agent")]
Timeout(Duration),
#[error("an event in the A2A agent's stream exceeded the {limit} byte limit")]
EventTooLarge { limit: usize },
#[error("the A2A agent's event stream sent nothing for {0:?}")]
StreamIdle(Duration),
#[error("the A2A agent answered {method} without an event stream")]
NotAStream { method: &'static str },
#[error("the A2A agent card is invalid: {0}")]
InvalidCard(&'static str),
#[error(
"the A2A agent card offers no JSONRPC or HTTP+JSON interface at protocol version 1.x, \
which are the only bindings and version this client speaks"
)]
NoCompatibleInterface,
#[error("the A2A agent card's interface URL is unusable: {0}")]
InvalidEndpoint(A2aUrlError),
#[error(
"refusing to send configured headers to the A2A agent's interface over plaintext \
http; the card must name https or a loopback host, or set \
allow_plaintext_credentials"
)]
PlaintextEndpoint,
#[error(
"refusing to send a push config's token or authentication to the A2A agent's interface \
over plaintext http; the card must name https or a loopback host, or set \
allow_plaintext_credentials"
)]
PlaintextPushCredentials,
#[error("{method} names an id that is empty, `.` or `..`")]
InvalidId { method: &'static str },
#[error("the A2A agent's reply to {method} could not be decoded")]
Decode { method: &'static str },
#[error("the A2A agent answered {method} with a reply for a different request id")]
MismatchedReplyId { method: &'static str },
#[error("the A2A agent answered {method} about a different task")]
MismatchedTask { method: &'static str },
#[error("the A2A agent answered {method} with {error}")]
Rpc {
method: &'static str,
error: A2aRpcError,
},
#[error(
"an A2A extension URI is empty or contains a comma, whitespace, or a character an HTTP \
header cannot carry"
)]
InvalidExtensionUri,
#[error(
"the A2A agent may have received the message but never answered; it may already be \
working on it, so it must not be resent automatically"
)]
SendIndeterminate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum A2aRpcError {
#[error("TaskNotFoundError (-32001)")]
TaskNotFound,
#[error("TaskNotCancelableError (-32002)")]
TaskNotCancelable,
#[error("PushNotificationNotSupportedError (-32003)")]
PushNotificationNotSupported,
#[error("UnsupportedOperationError (-32004)")]
UnsupportedOperation,
#[error("ContentTypeNotSupportedError (-32005)")]
ContentTypeNotSupported,
#[error("InvalidAgentResponseError (-32006)")]
InvalidAgentResponse,
#[error("ExtendedAgentCardNotConfiguredError (-32007)")]
ExtendedAgentCardNotConfigured,
#[error("ExtensionSupportRequiredError (-32008)")]
ExtensionSupportRequired,
#[error("VersionNotSupportedError (-32009)")]
VersionNotSupported,
#[error("JSON parse error (-32700)")]
ParseError,
#[error("invalid request (-32600)")]
InvalidRequest,
#[error("method not found (-32601)")]
MethodNotFound,
#[error("invalid params (-32602)")]
InvalidParams,
#[error("internal error (-32603)")]
InternalError,
#[error("JSON-RPC error {0}")]
Other(i32),
}
impl A2aRpcError {
pub fn from_code(code: i32) -> Self {
match code {
error_code::TASK_NOT_FOUND => Self::TaskNotFound,
error_code::TASK_NOT_CANCELABLE => Self::TaskNotCancelable,
error_code::PUSH_NOTIFICATION_NOT_SUPPORTED => Self::PushNotificationNotSupported,
error_code::UNSUPPORTED_OPERATION => Self::UnsupportedOperation,
error_code::CONTENT_TYPE_NOT_SUPPORTED => Self::ContentTypeNotSupported,
error_code::INVALID_AGENT_RESPONSE => Self::InvalidAgentResponse,
error_code::EXTENDED_CARD_NOT_CONFIGURED => Self::ExtendedAgentCardNotConfigured,
error_code::EXTENSION_SUPPORT_REQUIRED => Self::ExtensionSupportRequired,
error_code::VERSION_NOT_SUPPORTED => Self::VersionNotSupported,
error_code::PARSE_ERROR => Self::ParseError,
error_code::INVALID_REQUEST => Self::InvalidRequest,
error_code::METHOD_NOT_FOUND => Self::MethodNotFound,
error_code::INVALID_PARAMS => Self::InvalidParams,
error_code::INTERNAL_ERROR => Self::InternalError,
other => Self::Other(other),
}
}
pub fn code(self) -> i32 {
match self {
Self::TaskNotFound => error_code::TASK_NOT_FOUND,
Self::TaskNotCancelable => error_code::TASK_NOT_CANCELABLE,
Self::PushNotificationNotSupported => error_code::PUSH_NOTIFICATION_NOT_SUPPORTED,
Self::UnsupportedOperation => error_code::UNSUPPORTED_OPERATION,
Self::ContentTypeNotSupported => error_code::CONTENT_TYPE_NOT_SUPPORTED,
Self::InvalidAgentResponse => error_code::INVALID_AGENT_RESPONSE,
Self::ExtendedAgentCardNotConfigured => error_code::EXTENDED_CARD_NOT_CONFIGURED,
Self::ExtensionSupportRequired => error_code::EXTENSION_SUPPORT_REQUIRED,
Self::VersionNotSupported => error_code::VERSION_NOT_SUPPORTED,
Self::ParseError => error_code::PARSE_ERROR,
Self::InvalidRequest => error_code::INVALID_REQUEST,
Self::MethodNotFound => error_code::METHOD_NOT_FOUND,
Self::InvalidParams => error_code::INVALID_PARAMS,
Self::InternalError => error_code::INTERNAL_ERROR,
Self::Other(code) => code,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_code_round_trips() {
for code in (-32009..=-32001).chain([-32700, -32600, -32601, -32602, -32603, -1, 7]) {
assert_eq!(A2aRpcError::from_code(code).code(), code);
}
assert_eq!(A2aRpcError::from_code(-32001), A2aRpcError::TaskNotFound);
assert_eq!(A2aRpcError::from_code(42), A2aRpcError::Other(42));
}
}