Files
sausage/agent/src/history.rs
T
surma c728429d04 sdk, agent: review fixes (T18R)
- The serve loop ends the process after EOF or exit_after_response,
  also with background tasks or listeners.
- One schema generator per direction for the whole document, so types
  with the same name get distinct names.
- The agent gives the complete error (code, message, data) to the
  model, writes SKIP only after a successful request, caps tool output
  at 64 KiB, writes each history line in one write, and skips a damaged
  line.
- Endpoints skip blank and non-RPC lines. client_rs does not panic.
- #[must_use] on builders and the lock guard; #[schemars] attributes
  and duplicate hooks are compile errors; raw identifiers; -32000 for
  local SDK errors; x-sausage-params only for methods with params.
2026-10-23 02:38:35 +01:00

89 lines
3.0 KiB
Rust

//! History window selection and context-size error detection.
use crate::llm::Entry;
/// Recognize provider errors that permit a retry with fewer complete turns.
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 status.split_whitespace().next() == Some("413") {
return true;
}
if status.split_whitespace().next() != Some("400") {
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)))
}
/// Find a user-turn boundary without discarding any of the current turn.
pub fn next_turn_start(history: &[Entry], from: usize, current: usize) -> Option<usize> {
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 413: {\"type\":\"request_too_large\"}"));
assert!(is_too_large("HTTP 413: {}"));
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);
}
}