pub(super) const MAX_SSE_FRAME_BYTES: usize = 8 * 1024 * 1024;
pub(super) const MAX_MCP_RESPONSE_BYTES: usize = 16 * 1024 * 1024;
pub(super) fn is_mcp_stale_session_body(body: &str) -> bool {
let body = body.to_ascii_lowercase();
body.contains("session") && (body.contains("expired") || body.contains("invalid"))
}
pub(super) fn is_mcp_stale_session_error(err: &anyhow::Error) -> bool {
let err = format!("{err:#}");
let lower_err = err.to_ascii_lowercase();
err.contains("MCP Streamable HTTP session expired")
|| err.contains("MCP session expired")
|| err.contains("SSE transport closed")
|| (err.contains("MCP SSE POST send failed") && is_connection_closed_error_text(&lower_err))
|| is_mcp_stale_session_body(&err)
}
pub(super) fn is_connection_closed_error_text(err: &str) -> bool {
err.contains("connection closed")
|| err.contains("connection reset")
|| err.contains("broken pipe")
|| err.contains("unexpected eof")
|| err.contains("forcibly closed")
}
pub(super) fn parse_sse_message_data(body: &str) -> Vec<Vec<u8>> {
let normalized = body.replace("\r\n", "\n");
let mut messages = Vec::new();
for block in normalized.split("\n\n") {
let mut event_type = "message";
let mut data = String::new();
for line in block.lines() {
if let Some(value) = sse_field_value(line, "event:") {
event_type = value;
} else if let Some(value) = sse_field_value(line, "data:") {
if !data.is_empty() {
data.push('\n');
}
data.push_str(value);
}
}
if event_type != "message" || data.trim().is_empty() {
continue;
}
messages.push(data.trim().as_bytes().to_vec());
}
messages
}
#[cfg(test)]
pub(super) fn find_sse_event_separator(buffer: &str) -> Option<(usize, usize)> {
match (buffer.find("\n\n"), buffer.find("\r\n\r\n")) {
(Some(lf), Some(crlf)) if crlf < lf => Some((crlf, 4)),
(Some(lf), _) => Some((lf, 2)),
(_, Some(crlf)) => Some((crlf, 4)),
_ => None,
}
}
pub(super) fn find_sse_event_separator_bytes(buffer: &[u8]) -> Option<(usize, usize)> {
let lf = buffer.windows(2).position(|w| w == b"\n\n");
let crlf = buffer.windows(4).position(|w| w == b"\r\n\r\n");
match (lf, crlf) {
(Some(lf), Some(crlf)) if crlf < lf => Some((crlf, 4)),
(Some(lf), _) => Some((lf, 2)),
(_, Some(crlf)) => Some((crlf, 4)),
_ => None,
}
}
pub(super) fn sse_field_value<'a>(line: &'a str, field: &str) -> Option<&'a str> {
let value = line.strip_prefix(field)?;
Some(value.strip_prefix(' ').unwrap_or(value))
}