gproxy_tokenize/tokenize/
extract.rs1use serde_json::Value;
5
6const TEXT_KEYS: &[&str] = &["text", "content", "input", "instructions", "system"];
8const SERIALIZE_KEYS: &[&str] = &["tools", "tool_choice", "system"];
11const MESSAGE_KEYS: &[&str] = &["messages", "contents", "input"];
13
14pub fn harvest(body: &[u8]) -> (Vec<String>, u64) {
20 let Ok(root) = serde_json::from_slice::<Value>(body) else {
21 return (Vec::new(), 0);
22 };
23 let mut texts = Vec::new();
24 let mut messages = 0u64;
25 walk(&root, &mut texts, &mut messages);
26 (texts, messages)
27}
28
29fn walk(value: &Value, texts: &mut Vec<String>, messages: &mut u64) {
30 match value {
31 Value::Object(map) => {
32 for (key, val) in map {
33 match val {
34 Value::String(s) if TEXT_KEYS.contains(&key.as_str()) => {
35 texts.push(s.clone());
36 }
37 _ if SERIALIZE_KEYS.contains(&key.as_str()) && !val.is_null() => {
38 texts.push(val.to_string());
39 }
40 Value::Array(arr) => {
41 if MESSAGE_KEYS.contains(&key.as_str()) {
42 *messages = (*messages).max(arr.len() as u64);
43 }
44 for item in arr {
45 walk(item, texts, messages);
46 }
47 }
48 Value::Object(_) => walk(val, texts, messages),
49 _ => {}
50 }
51 }
52 }
53 Value::Array(arr) => {
54 for item in arr {
55 walk(item, texts, messages);
56 }
57 }
58 _ => {}
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::harvest;
65
66 #[test]
67 fn harvest_claude_body() {
68 let body = serde_json::json!({
69 "model": "claude-sonnet-4",
70 "system": "be terse",
71 "messages": [
72 { "role": "user", "content": "hello there" },
73 { "role": "assistant", "content": [
74 { "type": "text", "text": "hi!" }
75 ]}
76 ],
77 "tools": [{ "name": "get_weather", "description": "weather" }]
78 })
79 .to_string();
80 let (texts, messages) = harvest(body.as_bytes());
81 assert_eq!(messages, 2);
82 assert!(texts.iter().any(|t| t == "hello there"));
83 assert!(texts.iter().any(|t| t == "hi!"));
84 assert!(texts.iter().any(|t| t == "be terse"));
85 assert!(texts.iter().any(|t| t.contains("get_weather")));
86 }
87
88 #[test]
89 fn harvest_openai_responses_string_input() {
90 let body = serde_json::json!({
91 "model": "deepseek-v4-flash",
92 "input": "Count these words."
93 })
94 .to_string();
95 let (texts, messages) = harvest(body.as_bytes());
96 assert_eq!(texts, ["Count these words."]);
97 assert_eq!(messages, 0);
98 }
99}