kaizen/collect/
model_from_json.rs1use serde_json::Value;
5
6fn non_empty(s: &str) -> Option<String> {
7 let t = s.trim();
8 if t.is_empty() {
9 None
10 } else {
11 Some(t.to_string())
12 }
13}
14
15pub fn from_object(obj: &serde_json::Map<String, Value>) -> Option<String> {
18 if let Some(s) = obj
19 .get("model")
20 .and_then(|v| v.as_str())
21 .and_then(non_empty)
22 {
23 return Some(s);
24 }
25 for (parent, key) in [
26 ("message", "model"),
27 ("metadata", "model"),
28 ("config", "model"),
29 ] {
30 if let Some(s) = obj
31 .get(parent)
32 .and_then(|o| o.get(key))
33 .and_then(|v| v.as_str())
34 .and_then(non_empty)
35 {
36 return Some(s);
37 }
38 }
39 None
40}
41
42pub fn from_line(line: &str) -> Option<String> {
44 let v: Value = serde_json::from_str(line.trim()).ok()?;
45 v.as_object().and_then(from_object)
46}
47
48pub fn from_value(v: &Value) -> Option<String> {
50 v.as_object().and_then(from_object)
51}
52
53pub fn channel_from_object(obj: &serde_json::Map<String, Value>) -> Option<String> {
55 if let Some(s) = obj
56 .get("origin")
57 .and_then(|o| o.get("channel"))
58 .and_then(|v| v.as_str())
59 .and_then(non_empty)
60 {
61 return Some(s);
62 }
63 for key in ["channel", "source"] {
64 if let Some(s) = obj.get(key).and_then(|v| v.as_str()).and_then(non_empty) {
65 return Some(s);
66 }
67 }
68 None
69}
70
71pub fn provider_from_object(obj: &serde_json::Map<String, Value>) -> Option<String> {
73 if let Some(s) = obj
74 .get("provider")
75 .and_then(|v| v.as_str())
76 .and_then(non_empty)
77 {
78 return Some(s);
79 }
80 obj.get("message")
81 .and_then(|m| m.get("provider"))
82 .and_then(|v| v.as_str())
83 .and_then(non_empty)
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn cursor_system_init() {
92 let j = r#"{"type":"system","subtype":"init","session_id":"s1","model":"Claude 4 Sonnet"}"#;
93 assert_eq!(from_line(j), Some("Claude 4 Sonnet".into()));
94 }
95
96 #[test]
97 fn openai_top_level_model() {
98 let j = r#"{"model":"gpt-4o","role":"assistant"}"#;
99 assert_eq!(from_line(j), Some("gpt-4o".into()));
100 }
101
102 #[test]
103 fn message_nested_model() {
104 let v = serde_json::json!({"message": {"model": "claude-3-5-sonnet-20241022"}});
105 assert_eq!(from_value(&v), Some("claude-3-5-sonnet-20241022".into()));
106 }
107
108 #[test]
109 fn empty_model_ignored() {
110 let v = serde_json::json!({"model": " "});
111 assert_eq!(from_value(&v), None);
112 }
113}