use crate::jsonrpc::{Message, Notification, Request, Response};
pub const MAX_FRAME_SIZE: usize = 16 * 1024 * 1024;
pub const MAX_HEADER_SIZE: usize = 512;
pub fn encode_frame(payload: &[u8]) -> Vec<u8> {
let header = format!("Content-Length: {}\r\n\r\n", payload.len());
let mut frame = Vec::with_capacity(header.len() + payload.len());
frame.extend_from_slice(header.as_bytes());
frame.extend_from_slice(payload);
frame
}
fn looks_like_base64(s: &str) -> bool {
if s.len() <= 64 {
return false;
}
let total = s.chars().count() as f64;
let base64_chars = s
.chars()
.filter(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '/' | '='))
.count() as f64;
base64_chars / total > 0.98
}
pub fn summarize_value(v: &serde_json::Value) -> serde_json::Value {
match v {
serde_json::Value::String(s) if looks_like_base64(s) => {
let prefix: String = s.chars().take(64).collect();
serde_json::Value::String(format!("<base64 len={} prefix={}>", s.len(), prefix))
}
serde_json::Value::Array(items) => {
serde_json::Value::Array(items.iter().map(summarize_value).collect())
}
serde_json::Value::Object(map) => serde_json::Value::Object(
map.iter()
.map(|(k, val)| (k.clone(), summarize_value(val)))
.collect(),
),
other => other.clone(),
}
}
pub fn summarize_message(msg: &Message) -> Message {
match msg {
Message::Request(req) => Message::Request(Request {
jsonrpc: req.jsonrpc.clone(),
id: req.id,
method: req.method.clone(),
params: summarize_value(&req.params),
}),
Message::Response(resp) => Message::Response(Response {
jsonrpc: resp.jsonrpc.clone(),
id: resp.id,
result: resp.result.as_ref().map(summarize_value),
error: resp.error.clone(),
}),
Message::Notification(notif) => Message::Notification(Notification {
jsonrpc: notif.jsonrpc.clone(),
method: notif.method.clone(),
params: summarize_value(¬if.params),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn summarize_keeps_text_structure() {
let v = json!({
"name": "hello",
"list": [1, 2, {"deep": "world"}],
});
assert_eq!(summarize_value(&v), v);
}
#[test]
fn summarize_keeps_short_base64() {
let s = "aGVsbG8=";
assert_eq!(summarize_value(&json!(s)), json!("aGVsbG8="));
}
#[test]
fn summarize_masks_long_base64() {
let unit = "WmVyb0xhdW5jaA==";
let b64 = unit.repeat(20);
let summarized = summarize_value(&json!(b64));
let s = summarized.as_str().expect("仍是字符串");
assert!(s.starts_with("<base64 len="), "应被摘要: {}", s);
assert!(s.contains(&b64[..16]), "摘要应包含前缀");
}
#[test]
fn summarize_keeps_long_text() {
let text = "这是普通的模型回复文本,".repeat(50);
assert_eq!(summarize_value(&json!(text)), json!(text));
}
#[test]
fn summarize_masks_pure_lowercase_long_string() {
let s = "a".repeat(80);
let summarized = summarize_value(&json!(s));
let out = summarized.as_str().expect("仍是字符串");
assert!(
out.starts_with("<base64 len="),
"纯小写长串应被摘要: {}",
out
);
}
#[test]
fn summarize_keeps_long_english_with_spaces() {
let text = "the quick brown fox jumps over the lazy dog ".repeat(10);
assert_eq!(summarize_value(&json!(text)), json!(text));
}
}