1use std::fmt;
14use std::sync::Arc;
15
16use serde::{Deserialize, Serialize};
17
18use crate::{
19 http_client,
20 memory::MemoryError,
21 observe::AdapterErrorBoundary,
22 provider_response::ProviderResponseError,
23 tool::{ToolErrorKind, ToolExecutionError},
24 vector_store::VectorStoreError,
25};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum ErrorKind {
31 Http,
36 Json,
38 Url,
40 Request,
42 Response,
44 Provider,
46 ProviderResponse,
50 Tool(ToolErrorKind),
52 MemoryBackend,
54 MemoryPolicy,
56 Internal,
58 Cancelled,
60 Timeout,
62 BusClosed,
65 HandlerUnavailable,
68 Divergence,
71 Denied,
74 Other,
76}
77
78impl ErrorKind {
79 pub fn code(&self) -> &'static str {
81 match self {
82 Self::Http => "http",
83 Self::Json => "json",
84 Self::Url => "url",
85 Self::Request => "request",
86 Self::Response => "response",
87 Self::Provider => "provider",
88 Self::ProviderResponse => "provider_response",
89 Self::Tool(_) => "tool",
90 Self::MemoryBackend => "memory_backend",
91 Self::MemoryPolicy => "memory_policy",
92 Self::Internal => "internal",
93 Self::Cancelled => "cancelled",
94 Self::Timeout => "timeout",
95 Self::BusClosed => "bus_closed",
96 Self::HandlerUnavailable => "handler_unavailable",
97 Self::Divergence => "divergence",
98 Self::Denied => "denied",
99 Self::Other => "other",
100 }
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113pub struct ErrorReport {
114 pub kind: ErrorKind,
116 pub retryable: bool,
118 pub message: String,
120 pub code: Option<String>,
125 pub http_status: Option<u16>,
127 pub refusal: bool,
129 pub source_chain: Vec<String>,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub request_id: Option<String>,
135 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub provider_response: Option<crate::provider_response::ProviderResponseError>,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub detail: Option<ErrorDetail>,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(tag = "detail", rename_all = "snake_case")]
149#[non_exhaustive]
150pub enum ErrorDetail {
151 MalformedToolInput(MalformedToolInput),
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159pub struct MalformedToolInput {
160 pub name: String,
162 pub id: crate::message::CallId,
165 pub raw: String,
167 pub error: String,
169}
170
171impl ErrorReport {
172 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
174 Self {
175 kind,
176 retryable: false,
177 message: message.into(),
178 code: None,
179 http_status: None,
180 refusal: false,
181 source_chain: Vec::new(),
182 request_id: None,
183 provider_response: None,
184 detail: None,
185 }
186 }
187
188 pub fn with_retryable(mut self, retryable: bool) -> Self {
190 self.retryable = retryable;
191 self
192 }
193
194 pub fn with_code(mut self, code: impl Into<String>) -> Self {
196 self.code = Some(code.into());
197 self
198 }
199
200 pub fn with_http_status(mut self, status: u16) -> Self {
202 self.http_status = Some(status);
203 self
204 }
205
206 pub fn refused(mut self) -> Self {
208 self.refusal = true;
209 self
210 }
211
212 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
214 self.request_id = Some(request_id.into());
215 self
216 }
217
218 pub fn with_detail(mut self, detail: ErrorDetail) -> Self {
220 self.detail = Some(detail);
221 self
222 }
223
224 pub const fn is_retryable(&self) -> bool {
226 self.retryable
227 }
228}
229
230impl ErrorReport {
231 pub fn provider_response_body(&self) -> Option<&str> {
233 self.provider_response
234 .as_ref()
235 .map(|response| response.body.as_str())
236 }
237
238 pub fn provider_response_json(&self) -> Result<Option<serde_json::Value>, serde_json::Error> {
240 crate::provider_response::json(self.provider_response_body())
241 }
242
243 pub fn provider_response_headers(&self) -> Option<&http::HeaderMap> {
245 self.provider_response
246 .as_ref()
247 .and_then(|response| response.headers.as_ref())
248 }
249
250 pub fn provider_response_status(&self) -> Option<http::StatusCode> {
252 self.http_status
253 .and_then(|status| http::StatusCode::from_u16(status).ok())
254 }
255
256 pub fn provider_request_id(&self) -> Option<&str> {
258 self.request_id.as_deref()
259 }
260}
261
262impl fmt::Display for ErrorReport {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 f.write_str(&self.message)
265 }
266}
267
268impl std::error::Error for ErrorReport {}
269
270pub const fn retryable_status(status: Option<u16>) -> bool {
277 match status {
278 None => false,
279 Some(408 | 425 | 429) => true,
280 Some(s) => s >= 500 && s <= 599,
281 }
282}
283
284pub fn transient_transport(error: &http_client::Error) -> bool {
289 match error {
290 http_client::Error::StreamEnded | http_client::Error::Instance(_) => true,
291 http_client::Error::Protocol(_)
292 | http_client::Error::InvalidHeaderValue(_)
293 | http_client::Error::NoHeaders
294 | http_client::Error::InvalidContentType(_) => false,
295 http_client::Error::InvalidStatusCodeWithDetails { status, .. } => {
296 retryable_status(Some(status.as_u16()))
297 }
298 }
299}
300
301pub(crate) fn source_chain(error: &(dyn std::error::Error + 'static)) -> Vec<String> {
304 let mut chain = Vec::new();
305 let mut current = error.source();
306 while let Some(source) = current {
307 chain.push(source.to_string());
308 current = source.source();
309 }
310 chain
311}
312
313#[cfg(not(target_family = "wasm"))]
315pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
316
317#[cfg(target_family = "wasm")]
319pub type BoxError = Box<dyn std::error::Error + 'static>;
320
321#[cfg(not(target_family = "wasm"))]
324pub type SharedError = Arc<dyn std::error::Error + Send + Sync + 'static>;
325
326#[cfg(target_family = "wasm")]
329pub type SharedError = Arc<dyn std::error::Error + 'static>;
330
331#[derive(Debug, Clone)]
359pub enum ProviderError {
360 Http(Arc<http_client::Error>),
364 Json(Arc<serde_json::Error>),
366 Url(url::ParseError),
368 Request(SharedError),
370 Response(String),
372 Provider(String),
374 ProviderResponse(ProviderResponseError),
377 InvalidAuthentication(ProviderResponseError),
379 CacheExpired {
383 name: String,
385 response: ProviderResponseError,
387 },
388 MismatchedDimensions {
392 provider: String,
394 requested: usize,
396 returned: usize,
398 },
399 MalformedToolInput(MalformedToolInput),
403 Relayed(Box<ErrorReport>),
406 Truncated,
409 DuplicateCallId(crate::message::CallId),
411}
412
413impl fmt::Display for ProviderError {
414 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415 match self {
416 Self::Http(error) => write!(f, "HttpError: {error}"),
417 Self::Json(error) => write!(f, "JsonError: {error}"),
418 Self::Url(error) => write!(f, "UrlError: {error}"),
419 Self::Request(error) => write!(f, "RequestError: {error}"),
420 Self::Response(message) => write!(f, "ResponseError: {message}"),
421 Self::Provider(message) => write!(f, "ProviderError: {message}"),
422 Self::ProviderResponse(response) => write!(f, "ProviderResponseError: {response}"),
423 Self::InvalidAuthentication(response) => {
424 write!(f, "invalid authentication: {response}")
425 }
426 Self::CacheExpired { name, response } => write!(
427 f,
428 "cached content `{name}` is expired or was deleted: {}",
429 response.body
430 ),
431 Self::MismatchedDimensions {
432 provider,
433 requested,
434 returned,
435 } => write!(
436 f,
437 "{provider} embedding response returned {returned}-dimension vectors, but the \
438 model was created with {requested} dimensions; this provider does not resize \
439 embeddings"
440 ),
441 Self::MalformedToolInput(input) => write!(
442 f,
443 "tool call `{}` arrived with malformed JSON input: {}",
444 input.name, input.error
445 ),
446 Self::Relayed(report) => f.write_str(&report.message),
447 Self::Truncated => {
448 f.write_str("ResponseError: the reply ended before the provider ended it")
449 }
450 Self::DuplicateCallId(id) => {
451 write!(f, "ResponseError: the provider named two tool calls `{id}`")
452 }
453 }
454 }
455}
456
457impl std::error::Error for ProviderError {
460 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
461 match self {
462 Self::Json(error) => Some(&**error),
463 Self::Url(error) => Some(error),
464 Self::Request(error) => Some(&**error),
465 _ => None,
466 }
467 }
468}
469
470impl From<url::ParseError> for ProviderError {
471 fn from(error: url::ParseError) -> Self {
472 Self::Url(error)
473 }
474}
475
476impl ProviderError {
477 pub fn request(reason: impl Into<BoxError>) -> Self {
479 Self::Request(Arc::from(reason.into()))
480 }
481
482 pub fn from_http_response(status: http::StatusCode, body: impl Into<String>) -> Self {
485 Self::ProviderResponse(ProviderResponseError::new(status, body))
486 }
487
488 pub fn from_provider_body(body: impl Into<String>) -> Self {
491 Self::ProviderResponse(ProviderResponseError::without_status(body))
492 }
493
494 pub fn from_transport_error(error: http_client::Error) -> Self {
498 match error {
499 http_client::Error::InvalidStatusCodeWithDetails {
500 status,
501 body,
502 headers,
503 } => Self::from_http_response(status, body).with_response_headers(Some(headers)),
504 other => Self::Http(Arc::new(other)),
505 }
506 }
507
508 pub fn kind(&self) -> ErrorKind {
510 match self {
511 Self::Http(_) => ErrorKind::Http,
512 Self::Json(_) => ErrorKind::Json,
513 Self::Url(_) => ErrorKind::Url,
514 Self::Request(_) => ErrorKind::Request,
515 Self::Response(_)
516 | Self::MismatchedDimensions { .. }
517 | Self::MalformedToolInput(_)
518 | Self::Truncated
519 | Self::DuplicateCallId(_) => ErrorKind::Response,
520 Self::Provider(_) => ErrorKind::Provider,
521 Self::ProviderResponse(_)
522 | Self::InvalidAuthentication(_)
523 | Self::CacheExpired { .. } => ErrorKind::ProviderResponse,
524 Self::Relayed(report) => report.kind,
525 }
526 }
527
528 pub fn is_retryable(&self) -> bool {
533 match self {
534 Self::Http(error) => transient_transport(error),
535 Self::ProviderResponse(response) => response.is_retryable(),
536 Self::Relayed(report) => report.retryable,
537 Self::Truncated => true,
540 _ => false,
541 }
542 }
543
544 pub fn provider_response(&self) -> Option<&ProviderResponseError> {
546 match self {
547 Self::ProviderResponse(response)
548 | Self::InvalidAuthentication(response)
549 | Self::CacheExpired { response, .. } => Some(response),
550 Self::Relayed(report) => report.provider_response.as_ref(),
551 _ => None,
552 }
553 }
554
555 pub fn provider_response_body(&self) -> Option<&str> {
558 self.provider_response()
559 .map(|response| response.body.as_str())
560 }
561
562 pub fn provider_response_json(&self) -> Result<Option<serde_json::Value>, serde_json::Error> {
565 crate::provider_response::json(self.provider_response_body())
566 }
567
568 pub fn provider_response_status(&self) -> Option<http::StatusCode> {
570 self.provider_response()
571 .and_then(|response| response.status)
572 }
573
574 pub fn provider_request_id(&self) -> Option<&str> {
576 self.provider_response()
577 .and_then(|response| response.provider_request_id.as_deref())
578 }
579
580 pub fn provider_response_headers(&self) -> Option<&http::HeaderMap> {
599 self.provider_response()
600 .and_then(|response| response.headers.as_ref())
601 }
602
603 pub fn with_provider_request_id(self, request_id: Option<String>) -> Self {
606 self.map_response(|response| match response.provider_request_id {
607 Some(_) => response,
608 None => response.with_provider_request_id(request_id),
609 })
610 }
611
612 pub fn with_response_headers(self, headers: Option<http::HeaderMap>) -> Self {
614 self.map_response(|response| match (&response.headers, headers) {
615 (None, Some(headers)) => response.with_headers(Some(headers)),
616 _ => response,
617 })
618 }
619
620 pub fn with_provider_status(self, status: Option<http::StatusCode>) -> Self {
623 self.map_response(|response| response.with_status(status))
624 }
625
626 pub fn with_provider_code(self, code: Option<String>) -> Self {
629 self.map_response(|response| response.with_code(code))
630 }
631
632 pub fn with_transient(self, transient: Option<bool>) -> Self {
635 self.map_response(|response| response.with_transient(transient))
636 }
637
638 pub fn report(&self) -> ErrorReport {
640 ErrorReport::from(self)
641 }
642
643 pub(crate) fn boundary(&self) -> AdapterErrorBoundary {
645 match (self, self.kind()) {
646 (Self::Http(error), _) => AdapterErrorBoundary::from_http(error),
647 (_, ErrorKind::Json | ErrorKind::Response) => AdapterErrorBoundary::Decode,
648 (_, ErrorKind::Provider | ErrorKind::ProviderResponse) => {
649 AdapterErrorBoundary::ProviderResponse
650 }
651 _ => AdapterErrorBoundary::Request,
652 }
653 }
654
655 fn map_response(
656 self,
657 map: impl FnOnce(ProviderResponseError) -> ProviderResponseError,
658 ) -> Self {
659 match self {
660 Self::ProviderResponse(response) => Self::ProviderResponse(map(response)),
661 Self::InvalidAuthentication(response) => Self::InvalidAuthentication(map(response)),
662 Self::CacheExpired { name, response } => Self::CacheExpired {
663 name,
664 response: map(response),
665 },
666 Self::Relayed(mut report) => {
669 if let Some(response) = report.provider_response.take() {
670 let response = map(response);
671 report.request_id = report
672 .request_id
673 .or_else(|| response.provider_request_id.clone());
674 report.http_status = report
675 .http_status
676 .or_else(|| response.status.map(|status| status.as_u16()));
677 report.code = report.code.or_else(|| response.machine_code());
678 report.refusal |= response.refusal;
679 report.provider_response = Some(response);
680 }
681 Self::Relayed(report)
682 }
683 other => other,
684 }
685 }
686}
687
688impl From<serde_json::Error> for ProviderError {
689 fn from(error: serde_json::Error) -> Self {
690 Self::Json(Arc::new(error))
691 }
692}
693
694impl From<BoxError> for ProviderError {
695 fn from(error: BoxError) -> Self {
696 Self::Request(Arc::from(error))
697 }
698}
699
700impl From<http_client::Error> for ProviderError {
701 fn from(error: http_client::Error) -> Self {
702 Self::from_transport_error(error)
703 }
704}
705
706#[derive(Debug, thiserror::Error)]
711#[error(transparent)]
712pub struct EncodeError(ProviderError);
713
714impl EncodeError {
715 pub fn request(reason: impl Into<BoxError>) -> Self {
717 Self(ProviderError::request(reason))
718 }
719}
720
721impl From<EncodeError> for ProviderError {
722 fn from(error: EncodeError) -> Self {
723 debug_assert_eq!(error.0.kind(), ErrorKind::Request);
724 error.0
725 }
726}
727
728impl From<http::Error> for EncodeError {
729 fn from(error: http::Error) -> Self {
730 Self::request(error)
731 }
732}
733
734impl From<serde_json::Error> for EncodeError {
735 fn from(error: serde_json::Error) -> Self {
736 Self::request(error)
737 }
738}
739
740impl From<crate::message::MessageError> for EncodeError {
741 fn from(error: crate::message::MessageError) -> Self {
742 Self::request(error)
743 }
744}
745
746impl From<BoxError> for EncodeError {
747 fn from(error: BoxError) -> Self {
748 Self(ProviderError::from(error))
749 }
750}
751
752impl From<http::Error> for ProviderError {
753 fn from(error: http::Error) -> Self {
754 Self::request(error)
755 }
756}
757
758impl From<&ProviderError> for ErrorReport {
759 fn from(error: &ProviderError) -> Self {
760 if let ProviderError::Relayed(report) = error {
761 return (**report).clone();
762 }
763 let response = error.provider_response();
764 ErrorReport {
765 kind: error.kind(),
766 retryable: error.is_retryable(),
767 message: error.to_string(),
768 code: response.and_then(ProviderResponseError::machine_code),
769 http_status: response
770 .and_then(|response| response.status)
771 .map(|status| status.as_u16()),
772 refusal: response.is_some_and(|response| response.refusal),
773 source_chain: source_chain(error),
774 request_id: response.and_then(|response| response.provider_request_id.clone()),
775 provider_response: response.cloned(),
776 detail: match error {
777 ProviderError::MalformedToolInput(input) => {
778 Some(ErrorDetail::MalformedToolInput(input.clone()))
779 }
780 _ => None,
781 },
782 }
783 }
784}
785
786impl From<ProviderError> for ErrorReport {
787 fn from(error: ProviderError) -> Self {
788 Self::from(&error)
789 }
790}
791
792impl ToolExecutionError {
793 pub fn report(&self) -> ErrorReport {
795 ErrorReport::from(self)
796 }
797}
798
799impl ToolExecutionError {
800 pub fn is_retryable(&self) -> bool {
805 self.retryable()
806 .or_else(|| self.kind().default_retryable())
807 .unwrap_or(false)
808 }
809}
810
811impl From<&ToolExecutionError> for ErrorReport {
812 fn from(error: &ToolExecutionError) -> Self {
813 ErrorReport {
814 kind: ErrorKind::Tool(error.kind()),
815 retryable: error.is_retryable(),
816 message: error.message().to_string(),
817 code: error.code().map(str::to_string),
818 http_status: error.http_status(),
819 refusal: error.is_refusal(),
820 source_chain: source_chain(error),
821 request_id: None,
822 provider_response: None,
823 detail: None,
824 }
825 }
826}
827
828impl From<ToolExecutionError> for ErrorReport {
829 fn from(error: ToolExecutionError) -> Self {
830 Self::from(&error)
831 }
832}
833
834impl MemoryError {
835 pub fn report(&self) -> ErrorReport {
837 ErrorReport::from(self)
838 }
839}
840
841impl From<&MemoryError> for ErrorReport {
842 fn from(error: &MemoryError) -> Self {
843 let kind = match error {
844 MemoryError::Backend(_) => ErrorKind::MemoryBackend,
845 MemoryError::Policy(_) => ErrorKind::MemoryPolicy,
846 MemoryError::Internal(_) => ErrorKind::Internal,
847 };
848 ErrorReport {
849 kind,
850 retryable: false,
851 message: error.to_string(),
852 code: None,
853 http_status: None,
854 refusal: false,
855 source_chain: source_chain(error),
856 request_id: None,
857 provider_response: None,
858 detail: None,
859 }
860 }
861}
862
863impl From<MemoryError> for ErrorReport {
864 fn from(error: MemoryError) -> Self {
865 Self::from(&error)
866 }
867}
868
869impl From<&VectorStoreError> for ErrorReport {
870 fn from(error: &VectorStoreError) -> Self {
871 let (kind, provider_response) = match error {
872 VectorStoreError::EmbeddingError(inner) => {
873 return Self {
874 message: error.to_string(),
875 source_chain: source_chain(error),
876 ..Self::from(inner)
877 };
878 }
879 VectorStoreError::JsonError(_) => (ErrorKind::Json, None),
880 VectorStoreError::DatastoreError(_) => (ErrorKind::Provider, None),
881 VectorStoreError::FilterError(_) | VectorStoreError::BuilderError(_) => {
882 (ErrorKind::Request, None)
883 }
884 VectorStoreError::MissingIdError(_) => (ErrorKind::Response, None),
885 VectorStoreError::Http(crate::http_client::Error::InvalidStatusCodeWithDetails {
886 status,
887 body,
888 headers,
889 }) => (
890 ErrorKind::ProviderResponse,
891 Some(
892 crate::provider_response::ProviderResponseError::new(*status, body.clone())
893 .with_headers(Some(headers.clone())),
894 ),
895 ),
896 VectorStoreError::Http(_) => (ErrorKind::Http, None),
897 VectorStoreError::ExternalAPIError(status, body) => (
900 ErrorKind::ProviderResponse,
901 Some(crate::provider_response::ProviderResponseError::new(
902 *status,
903 body.clone(),
904 )),
905 ),
906 };
907 let http_status = provider_response
908 .as_ref()
909 .and_then(|response| response.status.map(|status| status.as_u16()));
910 let retryable = match error {
911 VectorStoreError::Http(inner) => transient_transport(inner),
912 VectorStoreError::ExternalAPIError(..) => retryable_status(http_status),
913 _ => false,
914 };
915 let code = provider_response
916 .as_ref()
917 .and_then(|response| response.machine_code());
918 ErrorReport {
919 kind,
920 retryable,
921 message: error.to_string(),
922 code,
923 http_status,
924 refusal: provider_response.as_ref().is_some_and(|r| r.refusal),
925 source_chain: source_chain(error),
926 request_id: None,
927 provider_response,
928 detail: None,
929 }
930 }
931}
932
933impl From<VectorStoreError> for ErrorReport {
934 fn from(error: VectorStoreError) -> Self {
935 Self::from(&error)
936 }
937}
938
939const _: fn() = || {
942 fn assert_wire<T: Send + Sync + 'static + Serialize + serde::de::DeserializeOwned>() {}
943 assert_wire::<ErrorReport>();
944 assert_wire::<ErrorKind>();
945};
946
947#[cfg(test)]
948mod encode_tests;
949#[cfg(test)]
950mod tests;