ag_ui/client/error.rs
1//! Errors this crate can produce.
2
3use thiserror::Error;
4
5/// Everything that can go wrong while consuming an AG-UI stream.
6///
7/// The variant list is `#[non_exhaustive]`: new transports and validation rules
8/// are expected to add variants without a breaking release.
9#[derive(Debug, Error)]
10#[non_exhaustive]
11pub enum Error {
12 /// A frame's payload was not valid JSON, or not a valid [`Event`].
13 ///
14 /// [`Event`]: https://kimsoungryoul.github.io/ag-ui-rust/api/ag_ui/event/enum.Event.html
15 #[error("JSON error: {0}")]
16 Json(#[from] serde_json::Error),
17
18 /// An error raised by the core protocol types.
19 ///
20 /// Core's *protocol* violations do not arrive here: they are flattened into
21 /// [`Error::Protocol`] so that every rule a stream can break has one shape.
22 #[error(transparent)]
23 Core(crate::Error),
24
25 /// The bytes were not well-formed `text/event-stream`.
26 #[error("SSE decode error: {0}")]
27 Decode(String),
28
29 /// The stream parsed, but broke a rule the protocol requires — a content
30 /// event with no open message, a chunk with no id to attach to, events
31 /// after the run finished.
32 #[error("protocol violation: {0}")]
33 Protocol(String),
34
35 /// An RFC 6902 patch could not be applied.
36 ///
37 /// The target document is left exactly as it was: [`json_patch::patch`]
38 /// rolls back the operations it had already applied, so a rejected patch
39 /// never leaves half-mutated state behind.
40 #[error("{target} patch failed: {message}")]
41 Patch {
42 /// What the patch targeted — `"state"`, or `"activity <message id>"`.
43 target: String,
44 /// Why it was rejected, as reported by the patch engine.
45 message: String,
46 },
47
48 /// The application state did not deserialize into the caller's type.
49 ///
50 /// The raw JSON state is still updated and correct; only the typed view is
51 /// unavailable.
52 #[error("state does not match the expected type: {0}")]
53 State(#[source] serde_json::Error),
54
55 /// The agent reported `RUN_ERROR`.
56 #[error("run failed: {message}")]
57 Run {
58 /// What went wrong, for a human.
59 message: String,
60 /// The machine-readable code, when the agent sent one.
61 code: Option<String>,
62 },
63
64 /// The server answered with a status outside 2xx.
65 #[error("HTTP {status}: {body}")]
66 Http {
67 /// The status code.
68 status: u16,
69 /// The response body, truncated to a readable length.
70 body: String,
71 },
72
73 /// The transport failed — a connection reset, a DNS failure, a closed
74 /// channel. Carries the underlying error.
75 #[error("transport error: {0}")]
76 Transport(#[source] Box<dyn std::error::Error + Send + Sync>),
77
78 /// A transport was configured with something it cannot use: an unparseable
79 /// URL, an invalid header name.
80 #[error("invalid client configuration: {0}")]
81 Config(String),
82}
83
84impl From<crate::Error> for Error {
85 fn from(error: crate::Error) -> Self {
86 match error {
87 // A rule broken by the stream is a protocol violation whichever
88 // crate noticed it; a caller matching on `Protocol` should not have
89 // to know that `RunOutcome::validate` lives in core.
90 crate::Error::Protocol(message) => Self::Protocol(message),
91 other => Self::Core(other),
92 }
93 }
94}
95
96impl Error {
97 /// Wraps any error as a [`Error::Transport`].
98 ///
99 /// Transports for exotic runtimes have their own error types; this is how
100 /// they enter this crate's error enum without a variant of their own.
101 pub fn transport(error: impl std::error::Error + Send + Sync + 'static) -> Self {
102 Self::Transport(Box::new(error))
103 }
104
105 /// Builds a [`Error::Protocol`] from anything printable.
106 pub(crate) fn protocol(message: impl Into<String>) -> Self {
107 Self::Protocol(message.into())
108 }
109
110 /// Builds a [`Error::Decode`] from anything printable.
111 pub(crate) fn decode(message: impl Into<String>) -> Self {
112 Self::Decode(message.into())
113 }
114}
115
116/// Result alias used throughout this crate.
117pub type Result<T, E = Error> = core::result::Result<T, E>;