Skip to main content

agent_core/core/
protocol.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4/// Messages sent from client → server
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(tag = "type")]
7pub enum ClientMessage {
8    /// Send a user message to the conversation
9    #[serde(rename = "message")]
10    Message { content: String },
11
12    /// Execute a slash command
13    #[serde(rename = "command")]
14    Command { name: String, args: String },
15
16    /// Cancel the current streaming response
17    #[serde(rename = "cancel")]
18    Cancel,
19
20    /// Request current session state
21    #[serde(rename = "status")]
22    Status,
23
24    /// Request conversation history (for late-joining clients)
25    #[serde(rename = "history")]
26    History,
27}
28
29/// Messages sent from server → client
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(tag = "type")]
32pub enum ServerMessage {
33    /// Thinking tokens (streamed incrementally)
34    #[serde(rename = "thinking")]
35    Thinking { content: String },
36
37    /// Text tokens (streamed incrementally)
38    #[serde(rename = "text")]
39    Text { content: String },
40
41    /// A tool is ABOUT to be invoked (streaming JSON args)
42    #[serde(rename = "tool_use_start")]
43    ToolUseStart { tool_name: String },
44
45    /// Streaming chunk of the tool's JSON arguments
46    #[serde(rename = "tool_use_delta")]
47    ToolUseDelta(String),
48
49    /// A tool was invoked (JSON finished)
50    #[serde(rename = "tool_use")]
51    ToolUse {
52        tool_name: String,
53        tool_id: String,
54        input: Value,
55    },
56
57    /// Tool execution result
58    #[serde(rename = "tool_result")]
59    ToolResult { tool_id: String, result: String },
60
61    /// Tool execution incremental delta
62    #[serde(rename = "tool_result_delta")]
63    ToolResultDelta { tool_id: String, delta: String },
64
65    /// Token usage update
66    #[serde(rename = "usage")]
67    Usage {
68        input_tokens: u64,
69        output_tokens: u64,
70        /// Cache-write TTL split — optional, omitted when unknown.
71        #[serde(default, skip_serializing_if = "Option::is_none")]
72        cache_creation_5m: Option<u64>,
73        #[serde(default, skip_serializing_if = "Option::is_none")]
74        cache_creation_1h: Option<u64>,
75    },
76
77    /// Streaming complete for this turn
78    #[serde(rename = "done")]
79    Done,
80
81    /// Out-of-band advisory notice (e.g. cache-TTL downgrade warning).
82    /// Additive variant — old clients ignore unknown message types.
83    #[serde(rename = "notice")]
84    Notice { text: String },
85    #[serde(rename = "response_start")]
86    ResponseStart,
87    #[serde(rename = "response_reset")]
88    ResponseReset,
89
90    /// Error occurred
91    #[serde(rename = "error")]
92    Error { message: String },
93
94    /// System/info message (command responses, status)
95    #[serde(rename = "system")]
96    System { message: String },
97
98    /// Full conversation history (response to History request)
99    #[serde(rename = "history")]
100    HistoryResponse { messages: Vec<HistoryEntry> },
101
102    /// Server status
103    #[serde(rename = "status")]
104    StatusResponse {
105        model: String,
106        thinking: String,
107        streaming: bool,
108        session_id: String,
109        total_input_tokens: u64,
110        total_output_tokens: u64,
111        session_cost: f64,
112        connected_clients: usize,
113    },
114}
115
116/// A single entry in the conversation history (display-friendly)
117#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(tag = "role")]
119pub enum HistoryEntry {
120    #[serde(rename = "user")]
121    User { content: String, time: String },
122    #[serde(rename = "thinking")]
123    Thinking { content: String, time: String },
124    #[serde(rename = "text")]
125    Text { content: String, time: String },
126    #[serde(rename = "tool_use")]
127    ToolUse {
128        tool_name: String,
129        input: String,
130        time: String,
131    },
132    #[serde(rename = "tool_result")]
133    ToolResult { result: String, time: String },
134    #[serde(rename = "system")]
135    System { content: String, time: String },
136    #[serde(rename = "error")]
137    Error { content: String, time: String },
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use serde_json::json;
144
145    #[test]
146    fn test_client_message_message_roundtrip() {
147        let msg = ClientMessage::Message {
148            content: "Hello, world!".to_string(),
149        };
150
151        // Serialize to JSON
152        let json_value = serde_json::to_value(&msg).unwrap();
153
154        // Verify JSON structure
155        assert_eq!(json_value["type"], "message");
156        assert_eq!(json_value["content"], "Hello, world!");
157
158        // Deserialize back and verify
159        let deserialized: ClientMessage = serde_json::from_value(json_value).unwrap();
160        match deserialized {
161            ClientMessage::Message { content } => {
162                assert_eq!(content, "Hello, world!");
163            }
164            _ => panic!("Expected Message variant"),
165        }
166    }
167
168    #[test]
169    fn test_client_message_cancel_roundtrip() {
170        let msg = ClientMessage::Cancel;
171
172        // Serialize to JSON
173        let json_value = serde_json::to_value(&msg).unwrap();
174
175        // Verify JSON structure
176        assert_eq!(json_value["type"], "cancel");
177
178        // Deserialize back and verify
179        let deserialized: ClientMessage = serde_json::from_value(json_value).unwrap();
180        matches!(deserialized, ClientMessage::Cancel);
181    }
182
183    #[test]
184    fn test_client_message_command_roundtrip() {
185        let msg = ClientMessage::Command {
186            name: "clear".to_string(),
187            args: "".to_string(),
188        };
189
190        // Serialize to JSON
191        let json_value = serde_json::to_value(&msg).unwrap();
192
193        // Verify JSON structure
194        assert_eq!(json_value["type"], "command");
195        assert_eq!(json_value["name"], "clear");
196        assert_eq!(json_value["args"], "");
197
198        // Deserialize back and verify
199        let deserialized: ClientMessage = serde_json::from_value(json_value).unwrap();
200        match deserialized {
201            ClientMessage::Command { name, args } => {
202                assert_eq!(name, "clear");
203                assert_eq!(args, "");
204            }
205            _ => panic!("Expected Command variant"),
206        }
207    }
208
209    #[test]
210    fn test_server_message_text_roundtrip() {
211        let msg = ServerMessage::Text {
212            content: "Hello from server!".to_string(),
213        };
214
215        // Serialize to JSON
216        let json_value = serde_json::to_value(&msg).unwrap();
217
218        // Verify JSON structure
219        assert_eq!(json_value["type"], "text");
220        assert_eq!(json_value["content"], "Hello from server!");
221
222        // Deserialize back and verify
223        let deserialized: ServerMessage = serde_json::from_value(json_value).unwrap();
224        match deserialized {
225            ServerMessage::Text { content } => {
226                assert_eq!(content, "Hello from server!");
227            }
228            _ => panic!("Expected Text variant"),
229        }
230    }
231
232    #[test]
233    fn test_server_message_done_roundtrip() {
234        let msg = ServerMessage::Done;
235
236        // Serialize to JSON
237        let json_value = serde_json::to_value(&msg).unwrap();
238
239        // Verify JSON structure
240        assert_eq!(json_value["type"], "done");
241
242        // Deserialize back and verify
243        let deserialized: ServerMessage = serde_json::from_value(json_value).unwrap();
244        matches!(deserialized, ServerMessage::Done);
245    }
246
247    #[test]
248    fn test_server_message_error_roundtrip() {
249        let msg = ServerMessage::Error {
250            message: "Something went wrong".to_string(),
251        };
252
253        // Serialize to JSON
254        let json_value = serde_json::to_value(&msg).unwrap();
255
256        // Verify JSON structure
257        assert_eq!(json_value["type"], "error");
258        assert_eq!(json_value["message"], "Something went wrong");
259
260        // Deserialize back and verify
261        let deserialized: ServerMessage = serde_json::from_value(json_value).unwrap();
262        match deserialized {
263            ServerMessage::Error { message } => {
264                assert_eq!(message, "Something went wrong");
265            }
266            _ => panic!("Expected Error variant"),
267        }
268    }
269
270    #[test]
271    fn test_server_message_tool_use_roundtrip() {
272        let msg = ServerMessage::ToolUse {
273            tool_name: "execute_bash".to_string(),
274            tool_id: "tool_123".to_string(),
275            input: json!({"command": "ls -la", "timeout": 30}),
276        };
277
278        // Serialize to JSON
279        let json_value = serde_json::to_value(&msg).unwrap();
280
281        // Verify JSON structure
282        assert_eq!(json_value["type"], "tool_use");
283        assert_eq!(json_value["tool_name"], "execute_bash");
284        assert_eq!(json_value["tool_id"], "tool_123");
285        assert_eq!(json_value["input"]["command"], "ls -la");
286        assert_eq!(json_value["input"]["timeout"], 30);
287
288        // Deserialize back and verify
289        let deserialized: ServerMessage = serde_json::from_value(json_value).unwrap();
290        match deserialized {
291            ServerMessage::ToolUse {
292                tool_name,
293                tool_id,
294                input,
295            } => {
296                assert_eq!(tool_name, "execute_bash");
297                assert_eq!(tool_id, "tool_123");
298                assert_eq!(input["command"], "ls -la");
299                assert_eq!(input["timeout"], 30);
300            }
301            _ => panic!("Expected ToolUse variant"),
302        }
303    }
304
305    #[test]
306    fn test_server_message_usage_roundtrip() {
307        let msg = ServerMessage::Usage {
308            input_tokens: 150,
309            output_tokens: 75,
310            cache_creation_5m: None,
311            cache_creation_1h: None,
312        };
313
314        // Serialize to JSON
315        let json_value = serde_json::to_value(&msg).unwrap();
316
317        // Verify JSON structure
318        assert_eq!(json_value["type"], "usage");
319        assert_eq!(json_value["input_tokens"], 150);
320        assert_eq!(json_value["output_tokens"], 75);
321        // Split fields are skip-if-none: absent from the wire when unknown,
322        // so pre-existing protocol consumers see an unchanged shape.
323        assert!(json_value.get("cache_creation_5m").is_none());
324        assert!(json_value.get("cache_creation_1h").is_none());
325
326        // Deserialize back and verify
327        let deserialized: ServerMessage = serde_json::from_value(json_value).unwrap();
328        match deserialized {
329            ServerMessage::Usage {
330                input_tokens,
331                output_tokens,
332                cache_creation_5m,
333                cache_creation_1h,
334            } => {
335                assert_eq!(input_tokens, 150);
336                assert_eq!(output_tokens, 75);
337                assert_eq!(cache_creation_5m, None);
338                assert_eq!(cache_creation_1h, None);
339            }
340            _ => panic!("Expected Usage variant"),
341        }
342    }
343
344    #[test]
345    fn test_history_entry_user_roundtrip() {
346        let entry = HistoryEntry::User {
347            content: "User message".to_string(),
348            time: "2023-10-01T12:00:00Z".to_string(),
349        };
350
351        // Serialize to JSON
352        let json_value = serde_json::to_value(&entry).unwrap();
353
354        // Verify JSON structure
355        assert_eq!(json_value["role"], "user");
356        assert_eq!(json_value["content"], "User message");
357        assert_eq!(json_value["time"], "2023-10-01T12:00:00Z");
358
359        // Deserialize back and verify
360        let deserialized: HistoryEntry = serde_json::from_value(json_value).unwrap();
361        match deserialized {
362            HistoryEntry::User { content, time } => {
363                assert_eq!(content, "User message");
364                assert_eq!(time, "2023-10-01T12:00:00Z");
365            }
366            _ => panic!("Expected User variant"),
367        }
368    }
369}