use adk_core::AdkError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnthropicApiError {
pub error_type: String,
pub message: String,
pub status_code: u16,
pub request_id: Option<String>,
}
impl std::fmt::Display for AnthropicApiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Anthropic API error ({}): {} [type={}]",
self.status_code, self.message, self.error_type
)?;
if let Some(ref rid) = self.request_id {
write!(f, " [request_id={rid}]")?;
}
Ok(())
}
}
impl std::error::Error for AnthropicApiError {}
impl From<AnthropicApiError> for AdkError {
fn from(err: AnthropicApiError) -> Self {
use adk_core::{ErrorCategory, ErrorComponent};
let msg = err.to_string();
let category = match err.status_code {
401 => ErrorCategory::Unauthorized,
403 => ErrorCategory::Forbidden,
404 => ErrorCategory::NotFound,
408 => ErrorCategory::Timeout,
429 => ErrorCategory::RateLimited,
503 | 529 => ErrorCategory::Unavailable,
s if s >= 500 => ErrorCategory::Internal,
_ => ErrorCategory::InvalidInput,
};
let mut adk_err =
AdkError::new(ErrorComponent::Model, category, "model.anthropic.api_error", msg)
.with_upstream_status(err.status_code)
.with_provider("anthropic");
if let Some(ref rid) = err.request_id {
adk_err = adk_err.with_request_id(rid.clone());
}
adk_err
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConversionError {
UnsupportedMimeType(String),
InvalidToolDeclaration(String),
}
impl std::fmt::Display for ConversionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConversionError::UnsupportedMimeType(mime) => {
write!(f, "unsupported MIME type for Anthropic API: {mime}")
}
ConversionError::InvalidToolDeclaration(message) => write!(f, "{message}"),
}
}
}
impl std::error::Error for ConversionError {}
impl From<ConversionError> for AdkError {
fn from(err: ConversionError) -> Self {
AdkError::model(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_error_display_with_request_id() {
let err = AnthropicApiError {
error_type: "rate_limit_error".to_string(),
message: "Too many requests".to_string(),
status_code: 429,
request_id: Some("req_abc123".to_string()),
};
let display = err.to_string();
assert!(display.contains("429"));
assert!(display.contains("rate_limit_error"));
assert!(display.contains("Too many requests"));
assert!(display.contains("req_abc123"));
}
#[test]
fn test_api_error_display_without_request_id() {
let err = AnthropicApiError {
error_type: "invalid_request_error".to_string(),
message: "Invalid model".to_string(),
status_code: 400,
request_id: None,
};
let display = err.to_string();
assert!(display.contains("400"));
assert!(display.contains("invalid_request_error"));
assert!(display.contains("Invalid model"));
assert!(!display.contains("request_id"));
}
#[test]
fn test_api_error_into_adk_error() {
let err = AnthropicApiError {
error_type: "overloaded_error".to_string(),
message: "Server overloaded".to_string(),
status_code: 529,
request_id: Some("req_xyz".to_string()),
};
let adk_err: AdkError = err.into();
assert!(adk_err.is_model());
assert!(adk_err.to_string().contains("529"));
assert_eq!(adk_err.details.upstream_status_code, Some(529));
assert_eq!(adk_err.details.provider.as_deref(), Some("anthropic"));
assert!(adk_err.is_retryable(), "529 should be retryable (Unavailable category)");
}
#[test]
fn test_conversion_error_display() {
let err = ConversionError::UnsupportedMimeType("audio/wav".to_string());
assert!(err.to_string().contains("audio/wav"));
assert!(err.to_string().contains("unsupported MIME type"));
}
#[test]
fn test_conversion_error_into_adk_error() {
let err = ConversionError::UnsupportedMimeType("video/mp4".to_string());
let adk_err: AdkError = err.into();
assert!(adk_err.is_model());
assert!(adk_err.to_string().contains("video/mp4"));
}
}