1use std::fmt;
14use std::sync::Arc;
15
16use serde::{Deserialize, Serialize};
17
18use crate::{
19 completion::UnsupportedOption,
20 http_client,
21 observe::AdapterErrorBoundary,
22 provider_response::ProviderResponseError,
23 tool::{ToolErrorKind, ToolExecutionError},
24 vector_store::VectorStoreError,
25};
26
27mod retry;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum ErrorKind {
33 Http,
38 Json,
40 Url,
42 Request,
44 Response,
46 Provider,
48 ProviderResponse,
52 Tool(ToolErrorKind),
54 Internal,
56 Cancelled,
58 Timeout,
60 HandlerUnavailable,
63 Divergence,
66 Denied,
69 Other,
71}
72
73impl ErrorKind {
74 pub fn code(&self) -> &'static str {
76 match self {
77 Self::Http => "http",
78 Self::Json => "json",
79 Self::Url => "url",
80 Self::Request => "request",
81 Self::Response => "response",
82 Self::Provider => "provider",
83 Self::ProviderResponse => "provider_response",
84 Self::Tool(_) => "tool",
85 Self::Internal => "internal",
86 Self::Cancelled => "cancelled",
87 Self::Timeout => "timeout",
88 Self::HandlerUnavailable => "handler_unavailable",
89 Self::Divergence => "divergence",
90 Self::Denied => "denied",
91 Self::Other => "other",
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
105pub struct ErrorReport {
106 pub kind: ErrorKind,
108 pub retryable: bool,
110 pub message: String,
112 pub code: Option<String>,
117 pub http_status: Option<u16>,
119 pub refusal: bool,
121 pub source_chain: Vec<String>,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub request_id: Option<String>,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub provider_response: Option<crate::provider_response::ProviderResponseError>,
131}
132
133impl ErrorReport {
134 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
136 Self {
137 kind,
138 retryable: false,
139 message: message.into(),
140 code: None,
141 http_status: None,
142 refusal: false,
143 source_chain: Vec::new(),
144 request_id: None,
145 provider_response: None,
146 }
147 }
148
149 pub fn with_retryable(mut self, retryable: bool) -> Self {
151 self.retryable = retryable;
152 self
153 }
154
155 pub fn with_code(mut self, code: impl Into<String>) -> Self {
157 self.code = Some(code.into());
158 self
159 }
160
161 pub fn with_http_status(mut self, status: u16) -> Self {
163 self.http_status = Some(status);
164 self
165 }
166
167 pub fn refused(mut self) -> Self {
169 self.refusal = true;
170 self
171 }
172
173 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
175 self.request_id = Some(request_id.into());
176 self
177 }
178
179 pub const fn is_retryable(&self) -> bool {
181 self.retryable
182 }
183}
184
185impl ErrorReport {
186 pub fn provider_response_body(&self) -> Option<&str> {
188 self.provider_response
189 .as_ref()
190 .map(|response| response.body.as_str())
191 }
192
193 pub fn provider_response_json(&self) -> Result<Option<serde_json::Value>, serde_json::Error> {
195 crate::provider_response::json(self.provider_response_body())
196 }
197
198 pub fn provider_response_headers(&self) -> Option<&http::HeaderMap> {
200 self.provider_response
201 .as_ref()
202 .and_then(|response| response.headers.as_ref())
203 }
204
205 pub fn provider_response_status(&self) -> Option<http::StatusCode> {
207 self.http_status
208 .and_then(|status| http::StatusCode::from_u16(status).ok())
209 }
210
211 pub fn provider_request_id(&self) -> Option<&str> {
213 self.request_id.as_deref()
214 }
215}
216
217impl fmt::Display for ErrorReport {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 f.write_str(&self.message)
220 }
221}
222
223impl std::error::Error for ErrorReport {}
224
225pub const fn retryable_status(status: Option<u16>) -> bool {
232 match status {
233 None => false,
234 Some(408 | 425 | 429) => true,
235 Some(s) => s >= 500 && s <= 599,
236 }
237}
238
239pub fn transient_transport(error: &http_client::Error) -> bool {
245 match error {
246 http_client::Error::StreamEnded | http_client::Error::Instance(_) => true,
247 http_client::Error::InvalidStatusCodeWithDetails { status, .. } => {
248 retryable_status(Some(status.as_u16()))
249 }
250 _ => false,
251 }
252}
253
254pub(crate) fn source_chain(error: &(dyn std::error::Error + 'static)) -> Vec<String> {
257 let mut chain = Vec::new();
258 let mut current = error.source();
259 while let Some(source) = current {
260 chain.push(source.to_string());
261 current = source.source();
262 }
263 chain
264}
265
266#[cfg(not(target_family = "wasm"))]
268pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
269
270#[cfg(target_family = "wasm")]
272pub type BoxError = Box<dyn std::error::Error + 'static>;
273
274#[cfg(not(target_family = "wasm"))]
277pub type SharedError = Arc<dyn std::error::Error + Send + Sync + 'static>;
278
279#[cfg(target_family = "wasm")]
282pub type SharedError = Arc<dyn std::error::Error + 'static>;
283
284#[non_exhaustive]
312#[derive(Debug, Clone)]
313pub enum ProviderError {
314 Http(Arc<http_client::Error>),
318 Json(Arc<serde_json::Error>),
320 Url(url::ParseError),
322 Request(SharedError),
324 Response(String),
326 Provider(String),
331 ProviderResponse(ProviderResponseError),
334 InvalidAuthentication(ProviderResponseError),
336 CacheExpired {
340 name: String,
342 response: ProviderResponseError,
344 },
345 MismatchedDimensions {
349 provider: String,
351 requested: usize,
353 returned: usize,
355 },
356 Relayed(Box<ErrorReport>),
359 Truncated,
362 Stalled(std::time::Duration),
365 UnsupportedOption(UnsupportedOption),
369}
370
371impl fmt::Display for ProviderError {
372 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373 match self {
374 Self::Http(error) => write!(f, "HttpError: {error}"),
375 Self::Json(error) => write!(f, "JsonError: {error}"),
376 Self::Url(error) => write!(f, "UrlError: {error}"),
377 Self::Request(error) => write!(f, "RequestError: {error}"),
378 Self::Response(message) => write!(f, "ResponseError: {message}"),
379 Self::Provider(message) => write!(f, "ProviderError: {message}"),
380 Self::ProviderResponse(response) => write!(f, "ProviderResponseError: {response}"),
381 Self::InvalidAuthentication(response) => {
382 write!(f, "invalid authentication: {response}")
383 }
384 Self::CacheExpired { name, response } => write!(
385 f,
386 "cached content `{name}` is expired or was deleted: {}",
387 response.body
388 ),
389 Self::MismatchedDimensions {
390 provider,
391 requested,
392 returned,
393 } => write!(
394 f,
395 "{provider} embedding response returned {returned}-dimension vectors, but the \
396 model was created with {requested} dimensions; this provider does not resize \
397 embeddings"
398 ),
399 Self::Relayed(report) => f.write_str(&report.message),
400 Self::Truncated => {
401 f.write_str("ResponseError: the reply ended before the provider ended it")
402 }
403 Self::Stalled(idle) => write!(
404 f,
405 "TimeoutError: the streamed reply sent nothing for {}s",
406 idle.as_secs_f64()
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::from_body(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::Stalled(_) => ErrorKind::Timeout,
476 Self::ProviderResponse(_)
477 | Self::InvalidAuthentication(_)
478 | Self::CacheExpired { .. } => ErrorKind::ProviderResponse,
479 Self::Relayed(report) => report.kind,
480 }
481 }
482
483 pub fn is_retryable(&self) -> bool {
488 match self {
489 Self::Http(error) => transient_transport(error),
490 Self::ProviderResponse(response) => response.is_retryable(),
491 Self::Relayed(report) => report.retryable,
492 Self::Truncated => true,
495 Self::Stalled(_) => true,
497 _ => false,
498 }
499 }
500
501 pub fn provider_response(&self) -> Option<&ProviderResponseError> {
503 match self {
504 Self::ProviderResponse(response)
505 | Self::InvalidAuthentication(response)
506 | Self::CacheExpired { response, .. } => Some(response),
507 Self::Relayed(report) => report.provider_response.as_ref(),
508 _ => None,
509 }
510 }
511
512 pub fn provider_response_body(&self) -> Option<&str> {
515 self.provider_response()
516 .map(|response| response.body.as_str())
517 }
518
519 pub fn provider_response_json(&self) -> Result<Option<serde_json::Value>, serde_json::Error> {
522 crate::provider_response::json(self.provider_response_body())
523 }
524
525 pub fn provider_response_status(&self) -> Option<http::StatusCode> {
527 self.provider_response()
528 .and_then(|response| response.status)
529 }
530
531 pub fn provider_request_id(&self) -> Option<&str> {
533 self.provider_response()
534 .and_then(|response| response.provider_request_id.as_deref())
535 }
536
537 pub fn provider_response_headers(&self) -> Option<&http::HeaderMap> {
556 self.provider_response()
557 .and_then(|response| response.headers.as_ref())
558 }
559
560 pub fn with_provider_request_id(self, request_id: Option<String>) -> Self {
563 self.map_response(|response| match response.provider_request_id {
564 Some(_) => response,
565 None => response.with_provider_request_id(request_id),
566 })
567 }
568
569 pub fn with_response_headers(self, headers: Option<http::HeaderMap>) -> Self {
571 self.map_response(|response| match (&response.headers, headers) {
572 (None, Some(headers)) => response.with_headers(Some(headers)),
573 _ => response,
574 })
575 }
576
577 pub fn with_provider_status(self, status: Option<http::StatusCode>) -> Self {
580 self.map_response(|response| response.with_status(status))
581 }
582
583 pub fn with_provider_code(self, code: Option<String>) -> Self {
586 self.map_response(|response| response.with_code(code))
587 }
588
589 pub fn with_transient(self, transient: Option<bool>) -> Self {
592 self.map_response(|response| response.with_transient(transient))
593 }
594
595 pub fn report(&self) -> ErrorReport {
597 ErrorReport::from(self)
598 }
599
600 pub(crate) fn boundary(&self) -> AdapterErrorBoundary {
602 match (self, self.kind()) {
603 (Self::Http(error), _) => AdapterErrorBoundary::from_http(error),
604 (Self::Stalled(_), _) => AdapterErrorBoundary::Transport,
605 (_, ErrorKind::Json | ErrorKind::Response) => AdapterErrorBoundary::Decode,
606 (_, ErrorKind::Provider | ErrorKind::ProviderResponse) => {
607 AdapterErrorBoundary::ProviderResponse
608 }
609 _ => AdapterErrorBoundary::Request,
610 }
611 }
612
613 fn map_response(
614 self,
615 map: impl FnOnce(ProviderResponseError) -> ProviderResponseError,
616 ) -> Self {
617 match self {
618 Self::ProviderResponse(response) => Self::ProviderResponse(map(response)),
619 Self::InvalidAuthentication(response) => Self::InvalidAuthentication(map(response)),
620 Self::CacheExpired { name, response } => Self::CacheExpired {
621 name,
622 response: map(response),
623 },
624 Self::Relayed(mut report) => {
627 if let Some(response) = report.provider_response.take() {
628 let response = map(response);
629 report.request_id = report
630 .request_id
631 .or_else(|| response.provider_request_id.clone());
632 report.http_status = report
633 .http_status
634 .or_else(|| response.status.map(|status| status.as_u16()));
635 report.code = report.code.or_else(|| response.machine_code());
636 report.refusal |= response.refusal;
637 report.provider_response = Some(response);
638 }
639 Self::Relayed(report)
640 }
641 other => other,
642 }
643 }
644}
645
646impl From<serde_json::Error> for ProviderError {
647 fn from(error: serde_json::Error) -> Self {
648 Self::Json(Arc::new(error))
649 }
650}
651
652impl From<BoxError> for ProviderError {
653 fn from(error: BoxError) -> Self {
654 Self::Request(Arc::from(error))
655 }
656}
657
658impl From<http_client::Error> for ProviderError {
659 fn from(error: http_client::Error) -> Self {
660 Self::from_transport_error(error)
661 }
662}
663
664#[non_exhaustive]
670#[derive(Debug, thiserror::Error)]
671#[error(transparent)]
672pub struct EncodeError(ProviderError);
673
674impl EncodeError {
675 pub fn request(reason: impl Into<BoxError>) -> Self {
677 Self(ProviderError::request(reason))
678 }
679
680 pub fn unsupported(option: UnsupportedOption) -> Self {
682 Self(ProviderError::UnsupportedOption(option))
683 }
684
685 pub fn unsupported_option(&self) -> Option<&UnsupportedOption> {
687 match &self.0 {
688 ProviderError::UnsupportedOption(option) => Some(option),
689 _ => None,
690 }
691 }
692}
693
694impl From<EncodeError> for ProviderError {
695 fn from(error: EncodeError) -> Self {
696 debug_assert_eq!(error.0.kind(), ErrorKind::Request);
697 error.0
698 }
699}
700
701impl From<http::Error> for EncodeError {
702 fn from(error: http::Error) -> Self {
703 Self::request(error)
704 }
705}
706
707impl From<serde_json::Error> for EncodeError {
708 fn from(error: serde_json::Error) -> Self {
709 Self::request(error)
710 }
711}
712
713impl From<crate::message::MessageError> for EncodeError {
714 fn from(error: crate::message::MessageError) -> Self {
715 Self::request(error)
716 }
717}
718
719impl From<BoxError> for EncodeError {
720 fn from(error: BoxError) -> Self {
721 Self(ProviderError::from(error))
722 }
723}
724
725impl From<http::Error> for ProviderError {
726 fn from(error: http::Error) -> Self {
727 Self::request(error)
728 }
729}
730
731impl From<&ProviderError> for ErrorReport {
732 fn from(error: &ProviderError) -> Self {
733 if let ProviderError::Relayed(report) = error {
734 return (**report).clone();
735 }
736 let response = error.provider_response();
737 ErrorReport {
738 kind: error.kind(),
739 retryable: error.is_retryable(),
740 message: error.to_string(),
741 code: response.and_then(ProviderResponseError::machine_code),
742 http_status: response
743 .and_then(|response| response.status)
744 .map(|status| status.as_u16()),
745 refusal: response.is_some_and(|response| response.refusal),
746 source_chain: source_chain(error),
747 request_id: response.and_then(|response| response.provider_request_id.clone()),
748 provider_response: response.cloned(),
749 }
750 }
751}
752
753impl From<ProviderError> for ErrorReport {
754 fn from(error: ProviderError) -> Self {
755 Self::from(&error)
756 }
757}
758
759impl ToolExecutionError {
760 pub fn report(&self) -> ErrorReport {
762 ErrorReport::from(self)
763 }
764}
765
766impl ToolExecutionError {
767 pub fn is_retryable(&self) -> bool {
772 self.retryable()
773 .or_else(|| self.kind().default_retryable())
774 .unwrap_or(false)
775 }
776}
777
778impl From<&ToolExecutionError> for ErrorReport {
779 fn from(error: &ToolExecutionError) -> Self {
780 ErrorReport {
781 kind: ErrorKind::Tool(error.kind()),
782 retryable: error.is_retryable(),
783 message: error.message().to_string(),
784 code: error.code().map(str::to_string),
785 http_status: error.http_status(),
786 refusal: error.is_refusal(),
787 source_chain: source_chain(error),
788 request_id: None,
789 provider_response: None,
790 }
791 }
792}
793
794impl From<ToolExecutionError> for ErrorReport {
795 fn from(error: ToolExecutionError) -> Self {
796 Self::from(&error)
797 }
798}
799
800impl From<ErrorReport> for ToolExecutionError {
806 fn from(report: ErrorReport) -> Self {
807 let kind = match report.kind {
808 ErrorKind::Tool(kind) => kind,
809 ErrorKind::Timeout => ToolErrorKind::Timeout,
810 ErrorKind::Cancelled => ToolErrorKind::Cancelled,
811 ErrorKind::Denied => ToolErrorKind::PermissionDenied,
812 ErrorKind::HandlerUnavailable => ToolErrorKind::NotFound,
813 ErrorKind::Http => ToolErrorKind::Network,
814 ErrorKind::Provider | ErrorKind::ProviderResponse => ToolErrorKind::Provider,
815 ErrorKind::Json
816 | ErrorKind::Url
817 | ErrorKind::Request
818 | ErrorKind::Response
819 | ErrorKind::Internal
820 | ErrorKind::Divergence
821 | ErrorKind::Other => ToolErrorKind::Other,
822 };
823 let mut error = if report.refusal {
824 ToolExecutionError::refused(report.message.clone())
825 } else {
826 ToolExecutionError::new(kind, report.message.clone())
827 }
828 .with_retryable(report.retryable);
829 if let Some(code) = &report.code {
830 error = error.with_code(code.clone());
831 }
832 if let Some(status) = report.http_status {
833 error = error.with_http_status(status);
834 }
835 error.with_source(report)
836 }
837}
838
839impl From<&VectorStoreError> for ErrorReport {
840 fn from(error: &VectorStoreError) -> Self {
841 let (kind, provider_response) = match error {
842 VectorStoreError::EmbeddingError(inner) => {
843 return Self {
844 message: error.to_string(),
845 source_chain: source_chain(error),
846 ..Self::from(inner)
847 };
848 }
849 VectorStoreError::JsonError(_) => (ErrorKind::Json, None),
850 VectorStoreError::DatastoreError(_) => (ErrorKind::Provider, None),
851 VectorStoreError::FilterError(_) | VectorStoreError::SamplesOutOfRange { .. } => {
852 (ErrorKind::Request, None)
853 }
854 VectorStoreError::MissingIdError(_) => (ErrorKind::Response, None),
855 VectorStoreError::Http(crate::http_client::Error::InvalidStatusCodeWithDetails {
856 status,
857 body,
858 headers,
859 }) => (
860 ErrorKind::ProviderResponse,
861 Some(
862 crate::provider_response::ProviderResponseError::new(*status, body.clone())
863 .with_headers(Some(headers.clone())),
864 ),
865 ),
866 VectorStoreError::Http(_) => (ErrorKind::Http, None),
867 VectorStoreError::ExternalAPIError(status, body) => (
870 ErrorKind::ProviderResponse,
871 Some(crate::provider_response::ProviderResponseError::new(
872 *status,
873 body.clone(),
874 )),
875 ),
876 };
877 let http_status = provider_response
878 .as_ref()
879 .and_then(|response| response.status.map(|status| status.as_u16()));
880 let retryable = match error {
881 VectorStoreError::Http(inner) => transient_transport(inner),
882 VectorStoreError::ExternalAPIError(..) => retryable_status(http_status),
883 _ => false,
884 };
885 let code = provider_response
886 .as_ref()
887 .and_then(|response| response.machine_code());
888 ErrorReport {
889 kind,
890 retryable,
891 message: error.to_string(),
892 code,
893 http_status,
894 refusal: provider_response.as_ref().is_some_and(|r| r.refusal),
895 source_chain: source_chain(error),
896 request_id: None,
897 provider_response,
898 }
899 }
900}
901
902impl From<VectorStoreError> for ErrorReport {
903 fn from(error: VectorStoreError) -> Self {
904 Self::from(&error)
905 }
906}
907
908const _: fn() = || {
911 fn assert_wire<T: Send + Sync + 'static + Serialize + serde::de::DeserializeOwned>() {}
912 assert_wire::<ErrorReport>();
913 assert_wire::<ErrorKind>();
914};
915
916#[cfg(test)]
917mod encode_tests;
918#[cfg(test)]
919mod tests;