atomr_agents_coding_cli_vendor_antigravity/
parser.rs1use atomr_agents_coding_cli_core::{
6 CliEventParser, CliVendorKind, CodingCliEvent, FinishReason, ParseError,
7};
8use serde_json::Value;
9
10#[derive(Default)]
11pub struct AntigravityParser;
12
13impl AntigravityParser {
14 pub fn new() -> Self {
15 Self
16 }
17}
18
19impl CliEventParser for AntigravityParser {
20 fn parse_line(&mut self, line: &str) -> Result<Vec<CodingCliEvent>, ParseError> {
21 let s = line.trim();
22 if s.is_empty() {
23 return Ok(Vec::new());
24 }
25 let v: Value = serde_json::from_str(s)?;
26 Ok(normalize(&v))
27 }
28
29 fn flush(&mut self) -> Result<Vec<CodingCliEvent>, ParseError> {
30 Ok(Vec::new())
31 }
32}
33
34fn normalize(v: &Value) -> Vec<CodingCliEvent> {
35 match v.get("type").and_then(Value::as_str).unwrap_or("") {
36 "init" => vec![CodingCliEvent::SystemInit {
37 tools: Vec::new(),
38 mcp_servers: Vec::new(),
39 plugins: Vec::new(),
40 }],
41 "message" => {
42 if let Some(text) = v.pointer("/delta/text").and_then(Value::as_str) {
43 return vec![CodingCliEvent::AssistantTextDelta {
44 text: text.to_string(),
45 }];
46 }
47 if let Some(text) = v.get("text").and_then(Value::as_str) {
48 return vec![CodingCliEvent::AssistantTextDelta {
49 text: text.to_string(),
50 }];
51 }
52 vec![CodingCliEvent::RawVendorEvent {
53 vendor: CliVendorKind::Antigravity,
54 payload: v.clone(),
55 }]
56 }
57 "tool_use" => {
58 let id = v.get("id").and_then(Value::as_str).unwrap_or("").to_string();
59 let name = v.get("name").and_then(Value::as_str).unwrap_or("").to_string();
60 let input = v.get("args").or_else(|| v.get("input")).cloned().unwrap_or(Value::Null);
61 vec![CodingCliEvent::ToolCallStarted {
62 tool_call_id: id,
63 name,
64 input,
65 }]
66 }
67 "tool_result" => {
68 let id = v
69 .get("tool_use_id")
70 .or_else(|| v.get("id"))
71 .and_then(Value::as_str)
72 .unwrap_or("")
73 .to_string();
74 let output = v.get("content").or_else(|| v.get("output")).cloned();
75 let error = v.get("error").and_then(Value::as_str).map(|s| s.to_string());
76 vec![CodingCliEvent::ToolCallFinished {
77 tool_call_id: id,
78 output,
79 error,
80 }]
81 }
82 "usage" => {
83 let input_tokens = v
84 .pointer("/stats/input_tokens")
85 .or_else(|| v.get("input_tokens"))
86 .and_then(Value::as_u64)
87 .unwrap_or(0);
88 let output_tokens = v
89 .pointer("/stats/output_tokens")
90 .or_else(|| v.get("output_tokens"))
91 .and_then(Value::as_u64)
92 .unwrap_or(0);
93 vec![CodingCliEvent::Usage {
94 input_tokens,
95 output_tokens,
96 cost_usd: None,
97 }]
98 }
99 "result" => {
100 let text = v
101 .get("response")
102 .and_then(Value::as_str)
103 .or_else(|| v.get("result").and_then(Value::as_str))
104 .map(|s| s.to_string());
105 vec![CodingCliEvent::RunFinished {
106 reason: FinishReason::Completed,
107 result_text: text,
108 }]
109 }
110 _ => vec![CodingCliEvent::RawVendorEvent {
111 vendor: CliVendorKind::Antigravity,
112 payload: v.clone(),
113 }],
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn parses_message_delta() {
123 let mut p = AntigravityParser::new();
124 let ev = p.parse_line(r#"{"type":"message","delta":{"text":"hi"}}"#).unwrap();
125 assert!(matches!(&ev[0], CodingCliEvent::AssistantTextDelta { text } if text == "hi"));
126 }
127
128 #[test]
129 fn parses_result_response() {
130 let mut p = AntigravityParser::new();
131 let ev = p.parse_line(r#"{"type":"result","response":"done"}"#).unwrap();
132 assert!(matches!(
133 &ev[0],
134 CodingCliEvent::RunFinished {
135 reason: FinishReason::Completed,
136 result_text: Some(t),
137 } if t == "done"
138 ));
139 }
140
141 #[test]
142 fn parses_usage_stats() {
143 let mut p = AntigravityParser::new();
144 let ev = p
145 .parse_line(r#"{"type":"usage","stats":{"input_tokens":10,"output_tokens":5}}"#)
146 .unwrap();
147 assert!(matches!(
148 &ev[0],
149 CodingCliEvent::Usage { input_tokens: 10, output_tokens: 5, cost_usd: None }
150 ));
151 }
152
153 #[test]
154 fn unknown_falls_through() {
155 let mut p = AntigravityParser::new();
156 let ev = p.parse_line(r#"{"type":"weird","x":1}"#).unwrap();
157 assert!(matches!(&ev[0], CodingCliEvent::RawVendorEvent { .. }));
158 }
159}