use reqwest::StatusCode;
pub const AUDIO_CONTENT_PLACEHOLDER: &str = "[Audio content not supported]";
#[derive(Debug, Clone)]
pub struct ParsedDataUrl {
pub media_type: String,
pub data: String,
}
pub fn parse_data_url(url: &str) -> Option<ParsedDataUrl> {
if !url.starts_with("data:") {
return None;
}
let parts: Vec<&str> = url.splitn(2, ',').collect();
if parts.len() != 2 {
return None;
}
let media_type = parts[0]
.trim_start_matches("data:")
.trim_end_matches(";base64")
.to_string();
let data = parts[1].to_string();
Some(ParsedDataUrl { media_type, data })
}
pub fn is_request_too_large(status: StatusCode, error_text: &str, extra_patterns: &[&str]) -> bool {
let error_lower = error_text.to_lowercase();
if status == StatusCode::PAYLOAD_TOO_LARGE {
return true;
}
if status.is_client_error() {
if error_lower.contains("input is too long") || error_lower.contains("maximum context") {
return true;
}
if error_lower.contains("exceeds the maximum")
&& (error_lower.contains("token") || error_lower.contains("context"))
{
return true;
}
for pattern in extra_patterns {
if error_lower.contains(pattern) {
return true;
}
}
}
false
}
pub const ANTHROPIC_TOO_LARGE_PATTERNS: &[&str] = &[
"prompt is too long",
"request size exceeded",
"context length",
"too many tokens",
];
pub const GEMINI_TOO_LARGE_PATTERNS: &[&str] = &[
"request payload size exceeds",
"content too large",
"token limit exceeded",
];
pub fn is_model_not_found(status: StatusCode, error_text: &str, patterns: &[&str]) -> bool {
if status != StatusCode::NOT_FOUND {
return false;
}
let error_lower = error_text.to_lowercase();
for pattern in patterns {
if error_lower.contains(pattern) {
return true;
}
}
false
}
pub const ANTHROPIC_NOT_FOUND_PATTERNS: &[&str] = &["not_found_error"];
pub const GEMINI_NOT_FOUND_PATTERNS: &[&str] = &["not_found", "model"];
pub mod thinking_budget {
pub const LOW: u32 = 1024;
pub const MEDIUM: u32 = 4096;
pub const HIGH: u32 = 16384;
pub const XHIGH: u32 = 32768;
pub fn from_effort(effort: &str) -> Option<u32> {
match effort.to_lowercase().as_str() {
"low" => Some(LOW),
"medium" => Some(MEDIUM),
"high" => Some(HIGH),
"xhigh" => Some(XHIGH),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_data_url_valid() {
let result = parse_data_url("data:image/png;base64,iVBOR").unwrap();
assert_eq!(result.media_type, "image/png");
assert_eq!(result.data, "iVBOR");
}
#[test]
fn test_parse_data_url_jpeg() {
let result = parse_data_url("data:image/jpeg;base64,/9j/4AAQ").unwrap();
assert_eq!(result.media_type, "image/jpeg");
assert_eq!(result.data, "/9j/4AAQ");
}
#[test]
fn test_parse_data_url_not_data() {
assert!(parse_data_url("https://example.com/image.png").is_none());
}
#[test]
fn test_parse_data_url_no_comma() {
assert!(parse_data_url("data:image/jpeg;base64").is_none());
}
#[test]
fn test_is_request_too_large_413() {
assert!(is_request_too_large(StatusCode::PAYLOAD_TOO_LARGE, "", &[]));
}
#[test]
fn test_is_request_too_large_generic() {
assert!(is_request_too_large(
StatusCode::BAD_REQUEST,
"input is too long",
&[]
));
}
#[test]
fn test_is_request_too_large_anthropic() {
assert!(is_request_too_large(
StatusCode::BAD_REQUEST,
"prompt is too long: 100000 tokens",
ANTHROPIC_TOO_LARGE_PATTERNS
));
}
#[test]
fn test_is_request_too_large_gemini() {
assert!(is_request_too_large(
StatusCode::BAD_REQUEST,
"request payload size exceeds limit",
GEMINI_TOO_LARGE_PATTERNS
));
}
#[test]
fn test_is_model_not_found_with_pattern() {
assert!(is_model_not_found(
StatusCode::NOT_FOUND,
r#"{"error":{"type":"not_found_error"}}"#,
ANTHROPIC_NOT_FOUND_PATTERNS
));
}
#[test]
fn test_is_model_not_found_no_match_without_pattern() {
assert!(!is_model_not_found(
StatusCode::NOT_FOUND,
"Endpoint not found",
ANTHROPIC_NOT_FOUND_PATTERNS
));
}
#[test]
fn test_is_model_not_found_not_404() {
assert!(!is_model_not_found(
StatusCode::BAD_REQUEST,
"model not found",
&[]
));
}
#[test]
fn test_is_model_not_found_gemini() {
assert!(is_model_not_found(
StatusCode::NOT_FOUND,
r#"{"error":{"status":"NOT_FOUND","message":"model foo"}}"#,
GEMINI_NOT_FOUND_PATTERNS
));
}
#[test]
fn test_thinking_budget_from_effort() {
assert_eq!(thinking_budget::from_effort("low"), Some(1024));
assert_eq!(thinking_budget::from_effort("medium"), Some(4096));
assert_eq!(thinking_budget::from_effort("HIGH"), Some(16384));
assert_eq!(thinking_budget::from_effort("xhigh"), Some(32768));
assert_eq!(thinking_budget::from_effort("unknown"), None);
}
}