zerolaunch_plugin_protocol/
codec.rs1use crate::jsonrpc::{Message, Notification, Request, Response};
6
7pub const MAX_FRAME_SIZE: usize = 16 * 1024 * 1024;
10
11pub const MAX_HEADER_SIZE: usize = 512;
14
15pub fn encode_frame(payload: &[u8]) -> Vec<u8> {
21 let header = format!("Content-Length: {}\r\n\r\n", payload.len());
22 let mut frame = Vec::with_capacity(header.len() + payload.len());
23 frame.extend_from_slice(header.as_bytes());
24 frame.extend_from_slice(payload);
25 frame
26}
27
28fn looks_like_base64(s: &str) -> bool {
35 if s.len() <= 64 {
36 return false;
37 }
38 let total = s.chars().count() as f64;
41 let base64_chars = s
42 .chars()
43 .filter(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '/' | '='))
44 .count() as f64;
45 base64_chars / total > 0.98
46}
47
48pub fn summarize_value(v: &serde_json::Value) -> serde_json::Value {
52 match v {
53 serde_json::Value::String(s) if looks_like_base64(s) => {
54 let prefix: String = s.chars().take(64).collect();
55 serde_json::Value::String(format!("<base64 len={} prefix={}>", s.len(), prefix))
56 }
57 serde_json::Value::Array(items) => {
58 serde_json::Value::Array(items.iter().map(summarize_value).collect())
59 }
60 serde_json::Value::Object(map) => serde_json::Value::Object(
61 map.iter()
62 .map(|(k, val)| (k.clone(), summarize_value(val)))
63 .collect(),
64 ),
65 other => other.clone(),
66 }
67}
68
69pub fn summarize_message(msg: &Message) -> Message {
72 match msg {
73 Message::Request(req) => Message::Request(Request {
74 jsonrpc: req.jsonrpc.clone(),
75 id: req.id,
76 method: req.method.clone(),
77 params: summarize_value(&req.params),
78 }),
79 Message::Response(resp) => Message::Response(Response {
80 jsonrpc: resp.jsonrpc.clone(),
81 id: resp.id,
82 result: resp.result.as_ref().map(summarize_value),
83 error: resp.error.clone(),
84 }),
85 Message::Notification(notif) => Message::Notification(Notification {
86 jsonrpc: notif.jsonrpc.clone(),
87 method: notif.method.clone(),
88 params: summarize_value(¬if.params),
89 }),
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use serde_json::json;
97
98 #[test]
100 fn summarize_keeps_text_structure() {
101 let v = json!({
102 "name": "hello",
103 "list": [1, 2, {"deep": "world"}],
104 });
105 assert_eq!(summarize_value(&v), v);
106 }
107
108 #[test]
110 fn summarize_keeps_short_base64() {
111 let s = "aGVsbG8=";
112 assert_eq!(summarize_value(&json!(s)), json!("aGVsbG8="));
113 }
114
115 #[test]
117 fn summarize_masks_long_base64() {
118 let unit = "WmVyb0xhdW5jaA==";
121 let b64 = unit.repeat(20);
122 let summarized = summarize_value(&json!(b64));
123 let s = summarized.as_str().expect("仍是字符串");
124 assert!(s.starts_with("<base64 len="), "应被摘要: {}", s);
125 assert!(s.contains(&b64[..16]), "摘要应包含前缀");
126 }
127
128 #[test]
130 fn summarize_keeps_long_text() {
131 let text = "这是普通的模型回复文本,".repeat(50);
132 assert_eq!(summarize_value(&json!(text)), json!(text));
133 }
134
135 #[test]
137 fn summarize_masks_pure_lowercase_long_string() {
138 let s = "a".repeat(80);
139 let summarized = summarize_value(&json!(s));
140 let out = summarized.as_str().expect("仍是字符串");
141 assert!(
142 out.starts_with("<base64 len="),
143 "纯小写长串应被摘要: {}",
144 out
145 );
146 }
147
148 #[test]
150 fn summarize_keeps_long_english_with_spaces() {
151 let text = "the quick brown fox jumps over the lazy dog ".repeat(10);
152 assert_eq!(summarize_value(&json!(text)), json!(text));
153 }
154}