1use serde::{Deserialize, Serialize};
24use std::collections::HashMap;
25
26#[derive(Debug, Serialize, Deserialize)]
32#[serde(tag = "type")]
33#[serde(rename_all = "snake_case")]
34pub enum ClientRequest {
35 Chat {
37 content: String,
38 #[serde(default)]
39 context: Option<RequestContext>,
40 },
41
42 QuickAction {
44 action: QuickActionType,
45 content: String,
46 #[serde(default)]
47 context: Option<RequestContext>,
48 #[serde(default)]
49 instructions: Option<String>,
50 },
51
52 NewSession,
54
55 Status,
57
58 Memory {
60 operation: MemoryOperation,
61 },
62
63 LoadSession {
65 session_id: String,
66 },
67
68 ListSessions,
70}
71
72#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
74#[serde(rename_all = "snake_case")]
75pub enum QuickActionType {
76 Explain,
77 Fix,
78 GenerateTests,
79 Refactor,
80 Optimize,
81 Document,
82 Translate,
83}
84
85#[derive(Debug, Serialize, Deserialize)]
87#[serde(rename_all = "snake_case")]
88pub enum MemoryOperation {
89 List,
90 Search { query: String },
91 Add { content: String, category: Option<String> },
92 Clear,
93 Stats,
94}
95
96#[derive(Debug, Serialize, Deserialize, Default)]
98pub struct RequestContext {
99 #[serde(default)]
101 pub workspace: Option<String>,
102
103 #[serde(default)]
105 pub file: Option<String>,
106
107 #[serde(default)]
109 pub language: Option<String>,
110
111 #[serde(default)]
113 pub selection: Option<Selection>,
114
115 #[serde(default)]
117 pub diagnostics: Option<Vec<Diagnostic>>,
118
119 #[serde(default)]
121 pub extra_files: Option<Vec<String>>,
122}
123
124#[derive(Debug, Serialize, Deserialize)]
126pub struct Selection {
127 pub start: Position,
128 pub end: Position,
129}
130
131#[derive(Debug, Serialize, Deserialize)]
133pub struct Position {
134 pub line: u32,
135 pub character: u32,
136}
137
138#[derive(Debug, Serialize, Deserialize)]
140pub struct Diagnostic {
141 pub severity: String,
142 pub message: String,
143 pub range: Selection,
144 #[serde(default)]
145 pub source: Option<String>,
146 #[serde(default)]
147 pub code: Option<String>,
148}
149
150#[derive(Debug, Serialize, Deserialize)]
156#[serde(tag = "type")]
157#[serde(rename_all = "snake_case")]
158pub enum StreamEvent {
159 Text { content: String },
161
162 Thinking { content: String },
164
165 ToolUse {
167 id: String,
168 name: String,
169 input: serde_json::Value,
170 },
171
172 ToolResult {
174 tool_use_id: String,
175 content: String,
176 #[serde(default)]
177 success: bool,
178 },
179
180 WebSearchResult {
182 tool_use_id: String,
183 content: String,
184 },
185
186 Error {
188 message: String,
189 #[serde(default)]
190 code: Option<String>,
191 },
192
193 Done {
195 #[serde(default)]
196 usage: Option<Usage>,
197 },
198
199 SessionStarted {
201 session_id: String,
202 #[serde(default)]
203 memory_count: Option<usize>,
204 },
205
206 StatusResponse {
208 session_id: Option<String>,
209 message_count: usize,
210 total_tokens: u64,
211 is_streaming: bool,
212 },
213
214 MemoryList {
216 memories: Vec<MemoryEntry>,
217 },
218
219 MemoryStats {
221 total: usize,
222 by_category: HashMap<String, usize>,
223 },
224
225 SessionList {
227 sessions: Vec<SessionInfo>,
228 },
229
230 MemoryAdded {
232 category: String,
233 content: String,
234 },
235
236 Log {
238 level: String,
239 message: String,
240 },
241}
242
243#[derive(Debug, Serialize, Deserialize)]
245pub struct Usage {
246 pub input: u64,
247 pub output: u64,
248 #[serde(default)]
249 pub cache_read: Option<u64>,
250 #[serde(default)]
251 pub cache_write: Option<u64>,
252}
253
254#[derive(Debug, Serialize, Deserialize)]
256pub struct MemoryEntry {
257 pub id: String,
258 pub category: String,
259 pub content: String,
260 pub created_at: String,
261 #[serde(default)]
262 pub project: Option<String>,
263}
264
265#[derive(Debug, Serialize, Deserialize)]
267pub struct SessionInfo {
268 pub id: String,
269 #[serde(default)]
270 pub name: Option<String>,
271 pub created_at: String,
272 pub message_count: usize,
273 #[serde(default)]
274 pub last_used: Option<String>,
275}
276
277impl StreamEvent {
282 pub fn text(content: impl Into<String>) -> Self {
284 StreamEvent::Text { content: content.into() }
285 }
286
287 pub fn thinking(content: impl Into<String>) -> Self {
289 StreamEvent::Thinking { content: content.into() }
290 }
291
292 pub fn tool_use(id: impl Into<String>, name: impl Into<String>, input: serde_json::Value) -> Self {
294 StreamEvent::ToolUse {
295 id: id.into(),
296 name: name.into(),
297 input,
298 }
299 }
300
301 pub fn tool_result(tool_use_id: impl Into<String>, content: impl Into<String>, success: bool) -> Self {
303 StreamEvent::ToolResult {
304 tool_use_id: tool_use_id.into(),
305 content: content.into(),
306 success,
307 }
308 }
309
310 pub fn error(message: impl Into<String>) -> Self {
312 StreamEvent::Error { message: message.into(), code: None }
313 }
314
315 pub fn done(usage: Option<Usage>) -> Self {
317 StreamEvent::Done { usage }
318 }
319
320 pub fn session_started(session_id: impl Into<String>, memory_count: Option<usize>) -> Self {
322 StreamEvent::SessionStarted {
323 session_id: session_id.into(),
324 memory_count,
325 }
326 }
327
328 pub fn to_json_line(&self) -> String {
330 serde_json::to_string(self).unwrap_or_default() + "\n"
331 }
332}
333
334impl Usage {
335 pub fn new(input: u64, output: u64) -> Self {
337 Usage { input, output, cache_read: None, cache_write: None }
338 }
339
340 pub fn with_cache(input: u64, output: u64, cache_read: u64, cache_write: u64) -> Self {
342 Usage {
343 input,
344 output,
345 cache_read: Some(cache_read),
346 cache_write: Some(cache_write),
347 }
348 }
349}
350
351#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn test_serialize_chat_request() {
361 let request = ClientRequest::Chat {
362 content: "Hello".to_string(),
363 context: None,
364 };
365 let json = serde_json::to_string(&request).unwrap();
366 assert!(json.contains("\"type\":\"chat\""));
367 assert!(json.contains("\"content\":\"Hello\""));
368 }
369
370 #[test]
371 fn test_deserialize_chat_request() {
372 let json = "{\"type\":\"chat\",\"content\":\"Hello\",\"context\":null}";
373 let request: ClientRequest = serde_json::from_str(json).unwrap();
374 match request {
375 ClientRequest::Chat { content, .. } => {
376 assert_eq!(content, "Hello");
377 }
378 _ => panic!("Expected Chat request"),
379 }
380 }
381
382 #[test]
383 fn test_serialize_stream_event() {
384 let event = StreamEvent::text("Hello world");
385 let json = event.to_json_line();
386 assert!(json.contains("\"type\":\"text\""));
387 assert!(json.contains("\"content\":\"Hello world\""));
388 assert!(json.ends_with("\n"));
389 }
390
391 #[test]
392 fn test_serialize_tool_use() {
393 let event = StreamEvent::tool_use("tool_1", "read", serde_json::json!({"path": "src/main.rs"}));
394 let json = event.to_json_line();
395 assert!(json.contains("\"type\":\"tool_use\""));
396 assert!(json.contains("\"id\":\"tool_1\""));
397 assert!(json.contains("\"name\":\"read\""));
398 }
399
400 #[test]
401 fn test_request_context_with_file() {
402 let json = "{\"workspace\":\"/project\",\"file\":\"src/main.rs\",\"language\":\"rust\"}";
403 let context: RequestContext = serde_json::from_str(json).unwrap();
404 assert_eq!(context.workspace, Some("/project".to_string()));
405 assert_eq!(context.file, Some("src/main.rs".to_string()));
406 assert_eq!(context.language, Some("rust".to_string()));
407 }
408
409 #[test]
410 fn test_quick_action_request() {
411 let json = "{\"type\":\"quick_action\",\"action\":\"explain\",\"content\":\"fn main(){}\",\"context\":{\"language\":\"rust\"}}";
412 let request: ClientRequest = serde_json::from_str(json).unwrap();
413 match request {
414 ClientRequest::QuickAction { action, content, .. } => {
415 assert_eq!(action, QuickActionType::Explain);
416 assert_eq!(content, "fn main(){}");
417 }
418 _ => panic!("Expected QuickAction request"),
419 }
420 }
421}