1use std::fmt;
14use std::sync::Arc;
15
16use serde::{Deserialize, Serialize};
17
18use crate::{
19 completion::UnsupportedOption,
20 http_client,
21 memory::MemoryError,
22 observe::AdapterErrorBoundary,
23 provider_response::ProviderResponseError,
24 tool::{ToolErrorKind, ToolExecutionError},
25 vector_store::VectorStoreError,
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum ErrorKind {
32 Http,
37 Json,
39 Url,
41 Request,
43 Response,
45 Provider,
47 ProviderResponse,
51 Tool(ToolErrorKind),
53 MemoryBackend,
55 MemoryPolicy,
57 Internal,
59 Cancelled,
61 Timeout,
63 BusClosed,
66 HandlerUnavailable,
69 Divergence,
72 Denied,
75 Other,
77}
78
79impl ErrorKind {
80 pub fn code(&self) -> &'static str {
82 match self {
83 Self::Http => "http",
84 Self::Json => "json",
85 Self::Url => "url",
86 Self::Request => "request",
87 Self::Response => "response",
88 Self::Provider => "provider",
89 Self::ProviderResponse => "provider_response",
90 Self::Tool(_) => "tool",
91 Self::MemoryBackend => "memory_backend",
92 Self::MemoryPolicy => "memory_policy",
93 Self::Internal => "internal",
94 Self::Cancelled => "cancelled",
95 Self::Timeout => "timeout",
96 Self::BusClosed => "bus_closed",
97 Self::HandlerUnavailable => "handler_unavailable",
98 Self::Divergence => "divergence",
99 Self::Denied => "denied",
100 Self::Other => "other",
101 }
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub struct ErrorReport {
115 pub kind: ErrorKind,
117 pub retryable: bool,
119 pub message: String,
121 pub code: Option<String>,
126 pub http_status: Option<u16>,
128 pub refusal: bool,
130 pub source_chain: Vec<String>,
132 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub request_id: Option<String>,
136 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub provider_response: Option<crate::provider_response::ProviderResponseError>,
140}
141
142impl ErrorReport {
143 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
145 Self {
146 kind,
147 retryable: false,
148 message: message.into(),
149 code: None,
150 http_status: None,
151 refusal: false,
152 source_chain: Vec::new(),
153 request_id: None,
154 provider_response: None,
155 }
156 }
157
158 pub fn with_retryable(mut self, retryable: bool) -> Self {
160 self.retryable = retryable;
161 self
162 }
163
164 pub fn with_code(mut self, code: impl Into<String>) -> Self {
166 self.code = Some(code.into());
167 self
168 }
169
170 pub fn with_http_status(mut self, status: u16) -> Self {
172 self.http_status = Some(status);
173 self
174 }
175
176 pub fn refused(mut self) -> Self {
178 self.refusal = true;
179 self
180 }
181
182 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
184 self.request_id = Some(request_id.into());
185 self
186 }
187
188 pub const fn is_retryable(&self) -> bool {
190 self.retryable
191 }
192}
193
194impl ErrorReport {
195 pub fn provider_response_body(&self) -> Option<&str> {
197 self.provider_response
198 .as_ref()
199 .map(|response| response.body.as_str())
200 }
201
202 pub fn provider_response_json(&self) -> Result<Option<serde_json::Value>, serde_json::Error> {
204 crate::provider_response::json(self.provider_response_body())
205 }
206
207 pub fn provider_response_headers(&self) -> Option<&http::HeaderMap> {
209 self.provider_response
210 .as_ref()
211 .and_then(|response| response.headers.as_ref())
212 }
213
214 pub fn provider_response_status(&self) -> Option<http::StatusCode> {
216 self.http_status
217 .and_then(|status| http::StatusCode::from_u16(status).ok())
218 }
219
220 pub fn provider_request_id(&self) -> Option<&str> {
222 self.request_id.as_deref()
223 }
224}
225
226impl fmt::Display for ErrorReport {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 f.write_str(&self.message)
229 }
230}
231
232impl std::error::Error for ErrorReport {}
233
234pub const fn retryable_status(status: Option<u16>) -> bool {
241 match status {
242 None => false,
243 Some(408 | 425 | 429) => true,
244 Some(s) => s >= 500 && s <= 599,
245 }
246}
247
248pub fn transient_transport(error: &http_client::Error) -> bool {
253 match error {
254 http_client::Error::StreamEnded | http_client::Error::Instance(_) => true,
255 http_client::Error::Protocol(_)
256 | http_client::Error::InvalidHeaderValue(_)
257 | http_client::Error::NoHeaders
258 | http_client::Error::InvalidContentType(_) => false,
259 http_client::Error::InvalidStatusCodeWithDetails { status, .. } => {
260 retryable_status(Some(status.as_u16()))
261 }
262 }
263}
264
265pub(crate) fn source_chain(error: &(dyn std::error::Error + 'static)) -> Vec<String> {
268 let mut chain = Vec::new();
269 let mut current = error.source();
270 while let Some(source) = current {
271 chain.push(source.to_string());
272 current = source.source();
273 }
274 chain
275}
276
277#[cfg(not(target_family = "wasm"))]
279pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
280
281#[cfg(target_family = "wasm")]
283pub type BoxError = Box<dyn std::error::Error + 'static>;
284
285#[cfg(not(target_family = "wasm"))]
288pub type SharedError = Arc<dyn std::error::Error + Send + Sync + 'static>;
289
290#[cfg(target_family = "wasm")]
293pub type SharedError = Arc<dyn std::error::Error + 'static>;
294
295#[non_exhaustive]
323#[derive(Debug, Clone)]
324pub enum ProviderError {
325 Http(Arc<http_client::Error>),
329 Json(Arc<serde_json::Error>),
331 Url(url::ParseError),
333 Request(SharedError),
335 Response(String),
337 Provider(String),
339 ProviderResponse(ProviderResponseError),
342 InvalidAuthentication(ProviderResponseError),
344 CacheExpired {
348 name: String,
350 response: ProviderResponseError,
352 },
353 MismatchedDimensions {
357 provider: String,
359 requested: usize,
361 returned: usize,
363 },
364 Relayed(Box<ErrorReport>),
367 Truncated,
370 UnsupportedOption(UnsupportedOption),
374}
375
376impl fmt::Display for ProviderError {
377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378 match self {
379 Self::Http(error) => write!(f, "HttpError: {error}"),
380 Self::Json(error) => write!(f, "JsonError: {error}"),
381 Self::Url(error) => write!(f, "UrlError: {error}"),
382 Self::Request(error) => write!(f, "RequestError: {error}"),
383 Self::Response(message) => write!(f, "ResponseError: {message}"),
384 Self::Provider(message) => write!(f, "ProviderError: {message}"),
385 Self::ProviderResponse(response) => write!(f, "ProviderResponseError: {response}"),
386 Self::InvalidAuthentication(response) => {
387 write!(f, "invalid authentication: {response}")
388 }
389 Self::CacheExpired { name, response } => write!(
390 f,
391 "cached content `{name}` is expired or was deleted: {}",
392 response.body
393 ),
394 Self::MismatchedDimensions {
395 provider,
396 requested,
397 returned,
398 } => write!(
399 f,
400 "{provider} embedding response returned {returned}-dimension vectors, but the \
401 model was created with {requested} dimensions; this provider does not resize \
402 embeddings"
403 ),
404 Self::Relayed(report) => f.write_str(&report.message),
405 Self::Truncated => {
406 f.write_str("ResponseError: the reply ended before the provider ended it")
407 }
408 Self::UnsupportedOption(option) => write!(f, "RequestError: {option}"),
409 }
410 }
411}
412
413impl std::error::Error for ProviderError {
416 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
417 match self {
418 Self::Json(error) => Some(&**error),
419 Self::Url(error) => Some(error),
420 Self::Request(error) => Some(&**error),
421 _ => None,
422 }
423 }
424}
425
426impl From<url::ParseError> for ProviderError {
427 fn from(error: url::ParseError) -> Self {
428 Self::Url(error)
429 }
430}
431
432impl ProviderError {
433 pub fn request(reason: impl Into<BoxError>) -> Self {
435 Self::Request(Arc::from(reason.into()))
436 }
437
438 pub fn from_http_response(status: http::StatusCode, body: impl Into<String>) -> Self {
441 Self::ProviderResponse(ProviderResponseError::new(status, body))
442 }
443
444 pub fn from_provider_body(body: impl Into<String>) -> Self {
447 Self::ProviderResponse(ProviderResponseError::without_status(body))
448 }
449
450 pub fn from_transport_error(error: http_client::Error) -> Self {
454 match error {
455 http_client::Error::InvalidStatusCodeWithDetails {
456 status,
457 body,
458 headers,
459 } => Self::from_http_response(status, body).with_response_headers(Some(headers)),
460 other => Self::Http(Arc::new(other)),
461 }
462 }
463
464 pub fn kind(&self) -> ErrorKind {
466 match self {
467 Self::Http(_) => ErrorKind::Http,
468 Self::Json(_) => ErrorKind::Json,
469 Self::Url(_) => ErrorKind::Url,
470 Self::Request(_) | Self::UnsupportedOption(_) => ErrorKind::Request,
471 Self::Response(_) | Self::MismatchedDimensions { .. } | Self::Truncated => {
472 ErrorKind::Response
473 }
474 Self::Provider(_) => ErrorKind::Provider,
475 Self::ProviderResponse(_)
476 | Self::InvalidAuthentication(_)
477 | Self::CacheExpired { .. } => ErrorKind::ProviderResponse,
478 Self::Relayed(report) => report.kind,
479 }
480 }
481
482 pub fn is_retryable(&self) -> bool {
487 match self {
488 Self::Http(error) => transient_transport(error),
489 Self::ProviderResponse(response) => response.is_retryable(),
490 Self::Relayed(report) => report.retryable,
491 Self::Truncated => true,
494 _ => false,
495 }
496 }
497
498 pub fn provider_response(&self) -> Option<&ProviderResponseError> {
500 match self {
501 Self::ProviderResponse(response)
502 | Self::InvalidAuthentication(response)
503 | Self::CacheExpired { response, .. } => Some(response),
504 Self::Relayed(report) => report.provider_response.as_ref(),
505 _ => None,
506 }
507 }
508
509 pub fn provider_response_body(&self) -> Option<&str> {
512 self.provider_response()
513 .map(|response| response.body.as_str())
514 }
515
516 pub fn provider_response_json(&self) -> Result<Option<serde_json::Value>, serde_json::Error> {
519 crate::provider_response::json(self.provider_response_body())
520 }
521
522 pub fn provider_response_status(&self) -> Option<http::StatusCode> {
524 self.provider_response()
525 .and_then(|response| response.status)
526 }
527
528 pub fn provider_request_id(&self) -> Option<&str> {
530 self.provider_response()
531 .and_then(|response| response.provider_request_id.as_deref())
532 }
533
534 pub fn provider_response_headers(&self) -> Option<&http::HeaderMap> {
553 self.provider_response()
554 .and_then(|response| response.headers.as_ref())
555 }
556
557 pub fn with_provider_request_id(self, request_id: Option<String>) -> Self {
560 self.map_response(|response| match response.provider_request_id {
561 Some(_) => response,
562 None => response.with_provider_request_id(request_id),
563 })
564 }
565
566 pub fn with_response_headers(self, headers: Option<http::HeaderMap>) -> Self {
568 self.map_response(|response| match (&response.headers, headers) {
569 (None, Some(headers)) => response.with_headers(Some(headers)),
570 _ => response,
571 })
572 }
573
574 pub fn with_provider_status(self, status: Option<http::StatusCode>) -> Self {
577 self.map_response(|response| response.with_status(status))
578 }
579
580 pub fn with_provider_code(self, code: Option<String>) -> Self {
583 self.map_response(|response| response.with_code(code))
584 }
585
586 pub fn with_transient(self, transient: Option<bool>) -> Self {
589 self.map_response(|response| response.with_transient(transient))
590 }
591
592 pub fn report(&self) -> ErrorReport {
594 ErrorReport::from(self)
595 }
596
597 pub(crate) fn boundary(&self) -> AdapterErrorBoundary {
599 match (self, self.kind()) {
600 (Self::Http(error), _) => AdapterErrorBoundary::from_http(error),
601 (_, ErrorKind::Json | ErrorKind::Response) => AdapterErrorBoundary::Decode,
602 (_, ErrorKind::Provider | ErrorKind::ProviderResponse) => {
603 AdapterErrorBoundary::ProviderResponse
604 }
605 _ => AdapterErrorBoundary::Request,
606 }
607 }
608
609 fn map_response(
610 self,
611 map: impl FnOnce(ProviderResponseError) -> ProviderResponseError,
612 ) -> Self {
613 match self {
614 Self::ProviderResponse(response) => Self::ProviderResponse(map(response)),
615 Self::InvalidAuthentication(response) => Self::InvalidAuthentication(map(response)),
616 Self::CacheExpired { name, response } => Self::CacheExpired {
617 name,
618 response: map(response),
619 },
620 Self::Relayed(mut report) => {
623 if let Some(response) = report.provider_response.take() {
624 let response = map(response);
625 report.request_id = report
626 .request_id
627 .or_else(|| response.provider_request_id.clone());
628 report.http_status = report
629 .http_status
630 .or_else(|| response.status.map(|status| status.as_u16()));
631 report.code = report.code.or_else(|| response.machine_code());
632 report.refusal |= response.refusal;
633 report.provider_response = Some(response);
634 }
635 Self::Relayed(report)
636 }
637 other => other,
638 }
639 }
640}
641
642impl From<serde_json::Error> for ProviderError {
643 fn from(error: serde_json::Error) -> Self {
644 Self::Json(Arc::new(error))
645 }
646}
647
648impl From<BoxError> for ProviderError {
649 fn from(error: BoxError) -> Self {
650 Self::Request(Arc::from(error))
651 }
652}
653
654impl From<http_client::Error> for ProviderError {
655 fn from(error: http_client::Error) -> Self {
656 Self::from_transport_error(error)
657 }
658}
659
660#[non_exhaustive]
666#[derive(Debug, thiserror::Error)]
667#[error(transparent)]
668pub struct EncodeError(ProviderError);
669
670impl EncodeError {
671 pub fn request(reason: impl Into<BoxError>) -> Self {
673 Self(ProviderError::request(reason))
674 }
675
676 pub fn unsupported(option: UnsupportedOption) -> Self {
678 Self(ProviderError::UnsupportedOption(option))
679 }
680
681 pub fn unsupported_option(&self) -> Option<&UnsupportedOption> {
683 match &self.0 {
684 ProviderError::UnsupportedOption(option) => Some(option),
685 _ => None,
686 }
687 }
688}
689
690impl From<EncodeError> for ProviderError {
691 fn from(error: EncodeError) -> Self {
692 debug_assert_eq!(error.0.kind(), ErrorKind::Request);
693 error.0
694 }
695}
696
697impl From<http::Error> for EncodeError {
698 fn from(error: http::Error) -> Self {
699 Self::request(error)
700 }
701}
702
703impl From<serde_json::Error> for EncodeError {
704 fn from(error: serde_json::Error) -> Self {
705 Self::request(error)
706 }
707}
708
709impl From<crate::message::MessageError> for EncodeError {
710 fn from(error: crate::message::MessageError) -> Self {
711 Self::request(error)
712 }
713}
714
715impl From<BoxError> for EncodeError {
716 fn from(error: BoxError) -> Self {
717 Self(ProviderError::from(error))
718 }
719}
720
721impl From<http::Error> for ProviderError {
722 fn from(error: http::Error) -> Self {
723 Self::request(error)
724 }
725}
726
727impl From<&ProviderError> for ErrorReport {
728 fn from(error: &ProviderError) -> Self {
729 if let ProviderError::Relayed(report) = error {
730 return (**report).clone();
731 }
732 let response = error.provider_response();
733 ErrorReport {
734 kind: error.kind(),
735 retryable: error.is_retryable(),
736 message: error.to_string(),
737 code: response.and_then(ProviderResponseError::machine_code),
738 http_status: response
739 .and_then(|response| response.status)
740 .map(|status| status.as_u16()),
741 refusal: response.is_some_and(|response| response.refusal),
742 source_chain: source_chain(error),
743 request_id: response.and_then(|response| response.provider_request_id.clone()),
744 provider_response: response.cloned(),
745 }
746 }
747}
748
749impl From<ProviderError> for ErrorReport {
750 fn from(error: ProviderError) -> Self {
751 Self::from(&error)
752 }
753}
754
755impl ToolExecutionError {
756 pub fn report(&self) -> ErrorReport {
758 ErrorReport::from(self)
759 }
760}
761
762impl ToolExecutionError {
763 pub fn is_retryable(&self) -> bool {
768 self.retryable()
769 .or_else(|| self.kind().default_retryable())
770 .unwrap_or(false)
771 }
772}
773
774impl From<&ToolExecutionError> for ErrorReport {
775 fn from(error: &ToolExecutionError) -> Self {
776 ErrorReport {
777 kind: ErrorKind::Tool(error.kind()),
778 retryable: error.is_retryable(),
779 message: error.message().to_string(),
780 code: error.code().map(str::to_string),
781 http_status: error.http_status(),
782 refusal: error.is_refusal(),
783 source_chain: source_chain(error),
784 request_id: None,
785 provider_response: None,
786 }
787 }
788}
789
790impl From<ToolExecutionError> for ErrorReport {
791 fn from(error: ToolExecutionError) -> Self {
792 Self::from(&error)
793 }
794}
795
796impl From<ErrorReport> for ToolExecutionError {
802 fn from(report: ErrorReport) -> Self {
803 let kind = match report.kind {
804 ErrorKind::Tool(kind) => kind,
805 ErrorKind::Timeout => ToolErrorKind::Timeout,
806 ErrorKind::Cancelled => ToolErrorKind::Cancelled,
807 ErrorKind::Denied => ToolErrorKind::PermissionDenied,
808 ErrorKind::HandlerUnavailable => ToolErrorKind::NotFound,
809 ErrorKind::Http => ToolErrorKind::Network,
810 ErrorKind::Provider | ErrorKind::ProviderResponse => ToolErrorKind::Provider,
811 ErrorKind::Json
812 | ErrorKind::Url
813 | ErrorKind::Request
814 | ErrorKind::Response
815 | ErrorKind::MemoryBackend
816 | ErrorKind::MemoryPolicy
817 | ErrorKind::Internal
818 | ErrorKind::BusClosed
819 | ErrorKind::Divergence
820 | ErrorKind::Other => ToolErrorKind::Other,
821 };
822 let mut error = if report.refusal {
823 ToolExecutionError::refused(report.message.clone())
824 } else {
825 ToolExecutionError::new(kind, report.message.clone())
826 }
827 .with_retryable(report.retryable);
828 if let Some(code) = &report.code {
829 error = error.with_code(code.clone());
830 }
831 if let Some(status) = report.http_status {
832 error = error.with_http_status(status);
833 }
834 error.with_source(report)
835 }
836}
837
838impl MemoryError {
839 pub fn report(&self) -> ErrorReport {
841 ErrorReport::from(self)
842 }
843}
844
845impl From<&MemoryError> for ErrorReport {
846 fn from(error: &MemoryError) -> Self {
847 let kind = match error {
848 MemoryError::Backend(_) => ErrorKind::MemoryBackend,
849 MemoryError::Policy(_) => ErrorKind::MemoryPolicy,
850 MemoryError::Internal(_) => ErrorKind::Internal,
851 };
852 ErrorReport {
853 kind,
854 retryable: false,
855 message: error.to_string(),
856 code: None,
857 http_status: None,
858 refusal: false,
859 source_chain: source_chain(error),
860 request_id: None,
861 provider_response: None,
862 }
863 }
864}
865
866impl From<MemoryError> for ErrorReport {
867 fn from(error: MemoryError) -> Self {
868 Self::from(&error)
869 }
870}
871
872impl From<&VectorStoreError> for ErrorReport {
873 fn from(error: &VectorStoreError) -> Self {
874 let (kind, provider_response) = match error {
875 VectorStoreError::EmbeddingError(inner) => {
876 return Self {
877 message: error.to_string(),
878 source_chain: source_chain(error),
879 ..Self::from(inner)
880 };
881 }
882 VectorStoreError::JsonError(_) => (ErrorKind::Json, None),
883 VectorStoreError::DatastoreError(_) => (ErrorKind::Provider, None),
884 VectorStoreError::FilterError(_) | VectorStoreError::SamplesOutOfRange { .. } => {
885 (ErrorKind::Request, None)
886 }
887 VectorStoreError::MissingIdError(_) => (ErrorKind::Response, None),
888 VectorStoreError::Http(crate::http_client::Error::InvalidStatusCodeWithDetails {
889 status,
890 body,
891 headers,
892 }) => (
893 ErrorKind::ProviderResponse,
894 Some(
895 crate::provider_response::ProviderResponseError::new(*status, body.clone())
896 .with_headers(Some(headers.clone())),
897 ),
898 ),
899 VectorStoreError::Http(_) => (ErrorKind::Http, None),
900 VectorStoreError::ExternalAPIError(status, body) => (
903 ErrorKind::ProviderResponse,
904 Some(crate::provider_response::ProviderResponseError::new(
905 *status,
906 body.clone(),
907 )),
908 ),
909 };
910 let http_status = provider_response
911 .as_ref()
912 .and_then(|response| response.status.map(|status| status.as_u16()));
913 let retryable = match error {
914 VectorStoreError::Http(inner) => transient_transport(inner),
915 VectorStoreError::ExternalAPIError(..) => retryable_status(http_status),
916 _ => false,
917 };
918 let code = provider_response
919 .as_ref()
920 .and_then(|response| response.machine_code());
921 ErrorReport {
922 kind,
923 retryable,
924 message: error.to_string(),
925 code,
926 http_status,
927 refusal: provider_response.as_ref().is_some_and(|r| r.refusal),
928 source_chain: source_chain(error),
929 request_id: None,
930 provider_response,
931 }
932 }
933}
934
935impl From<VectorStoreError> for ErrorReport {
936 fn from(error: VectorStoreError) -> Self {
937 Self::from(&error)
938 }
939}
940
941const _: fn() = || {
944 fn assert_wire<T: Send + Sync + 'static + Serialize + serde::de::DeserializeOwned>() {}
945 assert_wire::<ErrorReport>();
946 assert_wire::<ErrorKind>();
947};
948
949#[cfg(test)]
950mod encode_tests;
951#[cfg(test)]
952mod tests;