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 UnsupportedOption(UnsupportedOption),
366}
367
368impl fmt::Display for ProviderError {
369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370 match self {
371 Self::Http(error) => write!(f, "HttpError: {error}"),
372 Self::Json(error) => write!(f, "JsonError: {error}"),
373 Self::Url(error) => write!(f, "UrlError: {error}"),
374 Self::Request(error) => write!(f, "RequestError: {error}"),
375 Self::Response(message) => write!(f, "ResponseError: {message}"),
376 Self::Provider(message) => write!(f, "ProviderError: {message}"),
377 Self::ProviderResponse(response) => write!(f, "ProviderResponseError: {response}"),
378 Self::InvalidAuthentication(response) => {
379 write!(f, "invalid authentication: {response}")
380 }
381 Self::CacheExpired { name, response } => write!(
382 f,
383 "cached content `{name}` is expired or was deleted: {}",
384 response.body
385 ),
386 Self::MismatchedDimensions {
387 provider,
388 requested,
389 returned,
390 } => write!(
391 f,
392 "{provider} embedding response returned {returned}-dimension vectors, but the \
393 model was created with {requested} dimensions; this provider does not resize \
394 embeddings"
395 ),
396 Self::Relayed(report) => f.write_str(&report.message),
397 Self::Truncated => {
398 f.write_str("ResponseError: the reply ended before the provider ended it")
399 }
400 Self::UnsupportedOption(option) => write!(f, "RequestError: {option}"),
401 }
402 }
403}
404
405impl std::error::Error for ProviderError {
408 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
409 match self {
410 Self::Json(error) => Some(&**error),
411 Self::Url(error) => Some(error),
412 Self::Request(error) => Some(&**error),
413 _ => None,
414 }
415 }
416}
417
418impl From<url::ParseError> for ProviderError {
419 fn from(error: url::ParseError) -> Self {
420 Self::Url(error)
421 }
422}
423
424impl ProviderError {
425 pub fn request(reason: impl Into<BoxError>) -> Self {
427 Self::Request(Arc::from(reason.into()))
428 }
429
430 pub fn from_http_response(status: http::StatusCode, body: impl Into<String>) -> Self {
433 Self::ProviderResponse(ProviderResponseError::new(status, body))
434 }
435
436 pub fn from_provider_body(body: impl Into<String>) -> Self {
439 Self::ProviderResponse(ProviderResponseError::from_body(body))
440 }
441
442 pub fn from_transport_error(error: http_client::Error) -> Self {
446 match error {
447 http_client::Error::InvalidStatusCodeWithDetails {
448 status,
449 body,
450 headers,
451 } => Self::from_http_response(status, body).with_response_headers(Some(headers)),
452 other => Self::Http(Arc::new(other)),
453 }
454 }
455
456 pub fn kind(&self) -> ErrorKind {
458 match self {
459 Self::Http(_) => ErrorKind::Http,
460 Self::Json(_) => ErrorKind::Json,
461 Self::Url(_) => ErrorKind::Url,
462 Self::Request(_) | Self::UnsupportedOption(_) => ErrorKind::Request,
463 Self::Response(_) | Self::MismatchedDimensions { .. } | Self::Truncated => {
464 ErrorKind::Response
465 }
466 Self::Provider(_) => ErrorKind::Provider,
467 Self::ProviderResponse(_)
468 | Self::InvalidAuthentication(_)
469 | Self::CacheExpired { .. } => ErrorKind::ProviderResponse,
470 Self::Relayed(report) => report.kind,
471 }
472 }
473
474 pub fn is_retryable(&self) -> bool {
479 match self {
480 Self::Http(error) => transient_transport(error),
481 Self::ProviderResponse(response) => response.is_retryable(),
482 Self::Relayed(report) => report.retryable,
483 Self::Truncated => true,
486 _ => false,
487 }
488 }
489
490 pub fn provider_response(&self) -> Option<&ProviderResponseError> {
492 match self {
493 Self::ProviderResponse(response)
494 | Self::InvalidAuthentication(response)
495 | Self::CacheExpired { response, .. } => Some(response),
496 Self::Relayed(report) => report.provider_response.as_ref(),
497 _ => None,
498 }
499 }
500
501 pub fn provider_response_body(&self) -> Option<&str> {
504 self.provider_response()
505 .map(|response| response.body.as_str())
506 }
507
508 pub fn provider_response_json(&self) -> Result<Option<serde_json::Value>, serde_json::Error> {
511 crate::provider_response::json(self.provider_response_body())
512 }
513
514 pub fn provider_response_status(&self) -> Option<http::StatusCode> {
516 self.provider_response()
517 .and_then(|response| response.status)
518 }
519
520 pub fn provider_request_id(&self) -> Option<&str> {
522 self.provider_response()
523 .and_then(|response| response.provider_request_id.as_deref())
524 }
525
526 pub fn provider_response_headers(&self) -> Option<&http::HeaderMap> {
545 self.provider_response()
546 .and_then(|response| response.headers.as_ref())
547 }
548
549 pub fn with_provider_request_id(self, request_id: Option<String>) -> Self {
552 self.map_response(|response| match response.provider_request_id {
553 Some(_) => response,
554 None => response.with_provider_request_id(request_id),
555 })
556 }
557
558 pub fn with_response_headers(self, headers: Option<http::HeaderMap>) -> Self {
560 self.map_response(|response| match (&response.headers, headers) {
561 (None, Some(headers)) => response.with_headers(Some(headers)),
562 _ => response,
563 })
564 }
565
566 pub fn with_provider_status(self, status: Option<http::StatusCode>) -> Self {
569 self.map_response(|response| response.with_status(status))
570 }
571
572 pub fn with_provider_code(self, code: Option<String>) -> Self {
575 self.map_response(|response| response.with_code(code))
576 }
577
578 pub fn with_transient(self, transient: Option<bool>) -> Self {
581 self.map_response(|response| response.with_transient(transient))
582 }
583
584 pub fn report(&self) -> ErrorReport {
586 ErrorReport::from(self)
587 }
588
589 pub(crate) fn boundary(&self) -> AdapterErrorBoundary {
591 match (self, self.kind()) {
592 (Self::Http(error), _) => AdapterErrorBoundary::from_http(error),
593 (_, ErrorKind::Json | ErrorKind::Response) => AdapterErrorBoundary::Decode,
594 (_, ErrorKind::Provider | ErrorKind::ProviderResponse) => {
595 AdapterErrorBoundary::ProviderResponse
596 }
597 _ => AdapterErrorBoundary::Request,
598 }
599 }
600
601 fn map_response(
602 self,
603 map: impl FnOnce(ProviderResponseError) -> ProviderResponseError,
604 ) -> Self {
605 match self {
606 Self::ProviderResponse(response) => Self::ProviderResponse(map(response)),
607 Self::InvalidAuthentication(response) => Self::InvalidAuthentication(map(response)),
608 Self::CacheExpired { name, response } => Self::CacheExpired {
609 name,
610 response: map(response),
611 },
612 Self::Relayed(mut report) => {
615 if let Some(response) = report.provider_response.take() {
616 let response = map(response);
617 report.request_id = report
618 .request_id
619 .or_else(|| response.provider_request_id.clone());
620 report.http_status = report
621 .http_status
622 .or_else(|| response.status.map(|status| status.as_u16()));
623 report.code = report.code.or_else(|| response.machine_code());
624 report.refusal |= response.refusal;
625 report.provider_response = Some(response);
626 }
627 Self::Relayed(report)
628 }
629 other => other,
630 }
631 }
632}
633
634impl From<serde_json::Error> for ProviderError {
635 fn from(error: serde_json::Error) -> Self {
636 Self::Json(Arc::new(error))
637 }
638}
639
640impl From<BoxError> for ProviderError {
641 fn from(error: BoxError) -> Self {
642 Self::Request(Arc::from(error))
643 }
644}
645
646impl From<http_client::Error> for ProviderError {
647 fn from(error: http_client::Error) -> Self {
648 Self::from_transport_error(error)
649 }
650}
651
652#[non_exhaustive]
658#[derive(Debug, thiserror::Error)]
659#[error(transparent)]
660pub struct EncodeError(ProviderError);
661
662impl EncodeError {
663 pub fn request(reason: impl Into<BoxError>) -> Self {
665 Self(ProviderError::request(reason))
666 }
667
668 pub fn unsupported(option: UnsupportedOption) -> Self {
670 Self(ProviderError::UnsupportedOption(option))
671 }
672
673 pub fn unsupported_option(&self) -> Option<&UnsupportedOption> {
675 match &self.0 {
676 ProviderError::UnsupportedOption(option) => Some(option),
677 _ => None,
678 }
679 }
680}
681
682impl From<EncodeError> for ProviderError {
683 fn from(error: EncodeError) -> Self {
684 debug_assert_eq!(error.0.kind(), ErrorKind::Request);
685 error.0
686 }
687}
688
689impl From<http::Error> for EncodeError {
690 fn from(error: http::Error) -> Self {
691 Self::request(error)
692 }
693}
694
695impl From<serde_json::Error> for EncodeError {
696 fn from(error: serde_json::Error) -> Self {
697 Self::request(error)
698 }
699}
700
701impl From<crate::message::MessageError> for EncodeError {
702 fn from(error: crate::message::MessageError) -> Self {
703 Self::request(error)
704 }
705}
706
707impl From<BoxError> for EncodeError {
708 fn from(error: BoxError) -> Self {
709 Self(ProviderError::from(error))
710 }
711}
712
713impl From<http::Error> for ProviderError {
714 fn from(error: http::Error) -> Self {
715 Self::request(error)
716 }
717}
718
719impl From<&ProviderError> for ErrorReport {
720 fn from(error: &ProviderError) -> Self {
721 if let ProviderError::Relayed(report) = error {
722 return (**report).clone();
723 }
724 let response = error.provider_response();
725 ErrorReport {
726 kind: error.kind(),
727 retryable: error.is_retryable(),
728 message: error.to_string(),
729 code: response.and_then(ProviderResponseError::machine_code),
730 http_status: response
731 .and_then(|response| response.status)
732 .map(|status| status.as_u16()),
733 refusal: response.is_some_and(|response| response.refusal),
734 source_chain: source_chain(error),
735 request_id: response.and_then(|response| response.provider_request_id.clone()),
736 provider_response: response.cloned(),
737 }
738 }
739}
740
741impl From<ProviderError> for ErrorReport {
742 fn from(error: ProviderError) -> Self {
743 Self::from(&error)
744 }
745}
746
747impl ToolExecutionError {
748 pub fn report(&self) -> ErrorReport {
750 ErrorReport::from(self)
751 }
752}
753
754impl ToolExecutionError {
755 pub fn is_retryable(&self) -> bool {
760 self.retryable()
761 .or_else(|| self.kind().default_retryable())
762 .unwrap_or(false)
763 }
764}
765
766impl From<&ToolExecutionError> for ErrorReport {
767 fn from(error: &ToolExecutionError) -> Self {
768 ErrorReport {
769 kind: ErrorKind::Tool(error.kind()),
770 retryable: error.is_retryable(),
771 message: error.message().to_string(),
772 code: error.code().map(str::to_string),
773 http_status: error.http_status(),
774 refusal: error.is_refusal(),
775 source_chain: source_chain(error),
776 request_id: None,
777 provider_response: None,
778 }
779 }
780}
781
782impl From<ToolExecutionError> for ErrorReport {
783 fn from(error: ToolExecutionError) -> Self {
784 Self::from(&error)
785 }
786}
787
788impl From<ErrorReport> for ToolExecutionError {
794 fn from(report: ErrorReport) -> Self {
795 let kind = match report.kind {
796 ErrorKind::Tool(kind) => kind,
797 ErrorKind::Timeout => ToolErrorKind::Timeout,
798 ErrorKind::Cancelled => ToolErrorKind::Cancelled,
799 ErrorKind::Denied => ToolErrorKind::PermissionDenied,
800 ErrorKind::HandlerUnavailable => ToolErrorKind::NotFound,
801 ErrorKind::Http => ToolErrorKind::Network,
802 ErrorKind::Provider | ErrorKind::ProviderResponse => ToolErrorKind::Provider,
803 ErrorKind::Json
804 | ErrorKind::Url
805 | ErrorKind::Request
806 | ErrorKind::Response
807 | ErrorKind::Internal
808 | ErrorKind::Divergence
809 | ErrorKind::Other => ToolErrorKind::Other,
810 };
811 let mut error = if report.refusal {
812 ToolExecutionError::refused(report.message.clone())
813 } else {
814 ToolExecutionError::new(kind, report.message.clone())
815 }
816 .with_retryable(report.retryable);
817 if let Some(code) = &report.code {
818 error = error.with_code(code.clone());
819 }
820 if let Some(status) = report.http_status {
821 error = error.with_http_status(status);
822 }
823 error.with_source(report)
824 }
825}
826
827impl From<&VectorStoreError> for ErrorReport {
828 fn from(error: &VectorStoreError) -> Self {
829 let (kind, provider_response) = match error {
830 VectorStoreError::EmbeddingError(inner) => {
831 return Self {
832 message: error.to_string(),
833 source_chain: source_chain(error),
834 ..Self::from(inner)
835 };
836 }
837 VectorStoreError::JsonError(_) => (ErrorKind::Json, None),
838 VectorStoreError::DatastoreError(_) => (ErrorKind::Provider, None),
839 VectorStoreError::FilterError(_) | VectorStoreError::SamplesOutOfRange { .. } => {
840 (ErrorKind::Request, None)
841 }
842 VectorStoreError::MissingIdError(_) => (ErrorKind::Response, None),
843 VectorStoreError::Http(crate::http_client::Error::InvalidStatusCodeWithDetails {
844 status,
845 body,
846 headers,
847 }) => (
848 ErrorKind::ProviderResponse,
849 Some(
850 crate::provider_response::ProviderResponseError::new(*status, body.clone())
851 .with_headers(Some(headers.clone())),
852 ),
853 ),
854 VectorStoreError::Http(_) => (ErrorKind::Http, None),
855 VectorStoreError::ExternalAPIError(status, body) => (
858 ErrorKind::ProviderResponse,
859 Some(crate::provider_response::ProviderResponseError::new(
860 *status,
861 body.clone(),
862 )),
863 ),
864 };
865 let http_status = provider_response
866 .as_ref()
867 .and_then(|response| response.status.map(|status| status.as_u16()));
868 let retryable = match error {
869 VectorStoreError::Http(inner) => transient_transport(inner),
870 VectorStoreError::ExternalAPIError(..) => retryable_status(http_status),
871 _ => false,
872 };
873 let code = provider_response
874 .as_ref()
875 .and_then(|response| response.machine_code());
876 ErrorReport {
877 kind,
878 retryable,
879 message: error.to_string(),
880 code,
881 http_status,
882 refusal: provider_response.as_ref().is_some_and(|r| r.refusal),
883 source_chain: source_chain(error),
884 request_id: None,
885 provider_response,
886 }
887 }
888}
889
890impl From<VectorStoreError> for ErrorReport {
891 fn from(error: VectorStoreError) -> Self {
892 Self::from(&error)
893 }
894}
895
896const _: fn() = || {
899 fn assert_wire<T: Send + Sync + 'static + Serialize + serde::de::DeserializeOwned>() {}
900 assert_wire::<ErrorReport>();
901 assert_wire::<ErrorKind>();
902};
903
904#[cfg(test)]
905mod encode_tests;
906#[cfg(test)]
907mod tests;