Convert an arbitrary error into a [`MessageError`]. This mirrors the TS `MessageV2.fromError` function. It inspects the error chain for well-known provider error types and falls back to `Unknown`.
(e: anyhow::Error, provider_id: &str)
| 590 | /// This mirrors the TS `MessageV2.fromError` function. It inspects the error |
| 591 | /// chain for well-known provider error types and falls back to `Unknown`. |
| 592 | pub fn error_from_anyhow(e: anyhow::Error, provider_id: &str) -> MessageError { |
| 593 | let err_str = e.to_string(); |
| 594 | |
| 595 | // 1. AbortError – the operation was cancelled / aborted. |
| 596 | if err_str.contains("abort") || err_str.contains("cancelled") || err_str.contains("AbortError") |
| 597 | { |
| 598 | return MessageError::AbortedError { message: err_str }; |
| 599 | } |
| 600 | |
| 601 | // 2. OutputLengthError – model hit its max output token limit. |
| 602 | if err_str.contains("output length") |
| 603 | || err_str.contains("max_tokens") |
| 604 | || err_str.contains("output_length") |
| 605 | || err_str.contains("OutputLengthError") |
| 606 | { |
| 607 | return MessageError::OutputLengthError { message: err_str }; |
| 608 | } |
| 609 | |
| 610 | // 3. AuthError – API key / credential issues. |
| 611 | if err_str.contains("auth") |
| 612 | || err_str.contains("api key") |
| 613 | || err_str.contains("API key") |
| 614 | || err_str.contains("LoadAPIKeyError") |
| 615 | || err_str.contains("unauthorized") |
| 616 | || err_str.contains("Unauthorized") |
| 617 | { |
| 618 | return MessageError::AuthError { |
| 619 | provider_id: provider_id.to_string(), |
| 620 | message: err_str, |
| 621 | }; |
| 622 | } |
| 623 | |
| 624 | // 4. ECONNRESET / connection reset. |
| 625 | if err_str.contains("ECONNRESET") |
| 626 | || err_str.contains("connection reset") |
| 627 | || err_str.contains("Connection reset") |
| 628 | { |
| 629 | let mut metadata = HashMap::new(); |
| 630 | metadata.insert("code".to_string(), "ECONNRESET".to_string()); |
| 631 | metadata.insert("message".to_string(), err_str.clone()); |
| 632 | return MessageError::ApiError { |
| 633 | message: "Connection reset by server".to_string(), |
| 634 | status_code: None, |
| 635 | is_retryable: true, |
| 636 | response_headers: None, |
| 637 | response_body: None, |
| 638 | metadata: Some(metadata), |
| 639 | }; |
| 640 | } |
| 641 | |
| 642 | // 5. Try to downcast to ProviderError for structured handling. |
| 643 | if let Some(provider_err) = e.downcast_ref::<ProviderError>() { |
| 644 | let parsed = parse_api_call_error(provider_id, provider_err); |
| 645 | return match parsed { |
| 646 | ParsedAPICallError::ContextOverflow { |
| 647 | message, |
| 648 | response_body, |
| 649 | } => MessageError::ContextOverflowError { |
no test coverage detected