use std::time::Duration;
use a2a_lf::error_code;
use super::config::{A2aConfigError, A2aUrlError};
#[derive(Debug, thiserror::Error)]
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("the A2A agent card is invalid: {0}")]
InvalidCard(&'static str),
#[error(
"the A2A agent card offers no JSONRPC interface at protocol version 1.0, which is the \
only binding and version this client speaks"
)]
NoCompatibleInterface,
#[error("the A2A agent card's JSONRPC endpoint is unusable: {0}")]
InvalidEndpoint(A2aUrlError),
#[error(
"refusing to send configured headers to the A2A agent's JSONRPC endpoint over \
plaintext http; the card must name https or a loopback host, or set \
allow_plaintext_credentials"
)]
PlaintextEndpoint,
#[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(
"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));
}
}