//! History window selection and context-size error detection. use crate::llm::Entry; pub fn is_too_large(error: &str) -> bool { let Some((status, body)) = error .strip_prefix("HTTP ") .and_then(|error| error.split_once(':')) else { return false; }; if !matches!(status.split_whitespace().next(), Some("400" | "413")) { return false; } let body = body.to_ascii_lowercase(); body.contains("context_length_exceeded") || body.contains("maximum context length") || body.contains("context length exceeded") || body.contains("too large") || (body.contains("too long") && ["context", "input", "prompt"] .iter() .any(|term| body.contains(term))) } pub fn next_turn_start(history: &[Entry], from: usize, current: usize) -> Option { let end = current.min(history.len().checked_sub(1)?); (from..=end).find(|&index| matches!(&history[index], Entry::User { .. })) } #[cfg(test)] mod tests { use super::*; #[test] fn detects_context_size_errors_only_for_http_400_or_413() { assert!(is_too_large("HTTP 400: context_length_exceeded")); assert!(is_too_large("HTTP 413: prompt is too long")); assert!(is_too_large("HTTP 400: maximum context length")); assert!(is_too_large("HTTP 400: request is too large")); assert!(!is_too_large("HTTP 500: prompt is too long")); assert!(!is_too_large("HTTP 400: invalid token")); } #[test] fn history_skip_advances_by_whole_user_turns() { let history = vec![ Entry::User { text: "first".into(), }, Entry::ToolCall { id: "a".into(), name: "tool".into(), arguments: "{}".into(), }, Entry::ToolResult { id: "a".into(), output: "done".into(), }, Entry::User { text: "second".into(), }, Entry::ToolCall { id: "b".into(), name: "tool".into(), arguments: "{}".into(), }, Entry::ToolResult { id: "b".into(), output: "done".into(), }, Entry::User { text: "current".into(), }, ]; assert_eq!(next_turn_start(&history, 0, 6), Some(0)); assert_eq!(next_turn_start(&history, 1, 6), Some(3)); assert_eq!(next_turn_start(&history, 4, 6), Some(6)); assert_eq!(next_turn_start(&history, usize::MAX, 6).unwrap_or(6), 6); } }