1use crate::types::*;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7pub trait Message: Serialize + for<'de> Deserialize<'de> {
9 fn message_type(&self) -> &str;
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Request {
15 #[serde(rename = "type")]
16 pub message_type: String,
17
18 pub id: Id,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub session_id: Option<SessionId>,
22
23 pub payload: RequestPayload,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub metadata: Option<Metadata>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(tag = "action", rename_all = "snake_case")]
32pub enum RequestPayload {
33 Initialize(InitializeRequest),
34 Execute(ExecuteRequest),
35 Complete(CompleteRequest),
36 Cancel(CancelRequest),
37 GetStatus(GetStatusRequest),
38 Custom(Value),
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct InitializeRequest {
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub working_directory: Option<String>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub environment: Option<Vec<EnvironmentVariable>>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub capabilities: Option<Vec<Capability>>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct EnvironmentVariable {
57 pub name: String,
58 pub value: String,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ExecuteRequest {
64 pub command: String,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub arguments: Option<Vec<String>>,
68
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub input: Option<String>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub timeout_ms: Option<u64>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub working_directory: Option<String>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct CompleteRequest {
82 pub prompt: String,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub context: Option<CompletionContext>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub max_suggestions: Option<usize>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct CompletionContext {
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub file_path: Option<String>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub cursor_position: Option<CursorPosition>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub surrounding_code: Option<String>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct CursorPosition {
107 pub line: usize,
108 pub column: usize,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct CancelRequest {
114 pub target_id: Id,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub reason: Option<String>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct GetStatusRequest {
123 pub target_id: Id,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub include_details: Option<bool>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct Response {
132 #[serde(rename = "type")]
133 pub message_type: String,
134
135 pub id: Id,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub request_id: Option<Id>,
139
140 pub status: Status,
141
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub payload: Option<ResponsePayload>,
144
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub error: Option<ErrorDetail>,
147
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub metadata: Option<Metadata>,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154#[serde(tag = "result_type", rename_all = "snake_case")]
155pub enum ResponsePayload {
156 Initialize(InitializeResponse),
157 Execute(ExecuteResponse),
158 Complete(CompleteResponse),
159 Status(StatusResponse),
160 Stream(StreamResponse),
161 Custom(Value),
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct InitializeResponse {
167 pub session_id: SessionId,
168 pub version: String,
169 pub capabilities: Vec<Capability>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct ExecuteResponse {
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub output: Option<String>,
177
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub error_output: Option<String>,
180
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub exit_code: Option<i32>,
183
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub execution_time_ms: Option<u64>,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct CompleteResponse {
191 pub suggestions: Vec<CompletionSuggestion>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct CompletionSuggestion {
197 pub text: String,
198
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub description: Option<String>,
201
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub score: Option<f32>,
204
205 #[serde(skip_serializing_if = "Option::is_none")]
206 pub metadata: Option<SuggestionMetadata>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
214pub struct SuggestionMetadata {
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub source: Option<String>,
218
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub priority: Option<i32>,
222
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub category: Option<String>,
226
227 #[serde(flatten)]
229 pub extra: std::collections::HashMap<String, Value>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct StatusResponse {
235 pub status: Status,
236
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub progress: Option<Progress>,
239
240 #[serde(skip_serializing_if = "Option::is_none")]
241 pub details: Option<StatusDetails>,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
249pub struct StatusDetails {
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub error: Option<String>,
253
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub reason: Option<String>,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub description: Option<String>,
261
262 #[serde(flatten)]
264 pub extra: std::collections::HashMap<String, Value>,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct Progress {
270 pub current: usize,
271 pub total: Option<usize>,
272
273 #[serde(skip_serializing_if = "Option::is_none")]
274 pub percentage: Option<f32>,
275
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub message: Option<String>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct StreamResponse {
283 pub chunk: String,
284
285 #[serde(skip_serializing_if = "Option::is_none")]
286 pub stream_id: Option<String>,
287
288 pub is_final: bool,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct Event {
294 #[serde(rename = "type")]
295 pub message_type: String,
296
297 pub event_type: EventType,
298
299 #[serde(skip_serializing_if = "Option::is_none")]
300 pub session_id: Option<SessionId>,
301
302 pub payload: Value,
303
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub metadata: Option<Metadata>,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310#[serde(rename_all = "snake_case")]
311pub enum EventType {
312 Log,
313 Progress,
314 StateChange,
315 Error,
316 Warning,
317 Info,
318 Debug,
319 Custom(String),
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn test_suggestion_metadata_parsing() {
328 let json = r#"{
329 "source": "history",
330 "priority": 5,
331 "category": "command",
332 "custom_field": "custom_value"
333 }"#;
334
335 let metadata: SuggestionMetadata = serde_json::from_str(json).unwrap();
336 assert_eq!(metadata.source, Some("history".to_string()));
337 assert_eq!(metadata.priority, Some(5));
338 assert_eq!(metadata.category, Some("command".to_string()));
339 assert_eq!(
340 metadata.extra.get("custom_field").unwrap(),
341 &serde_json::json!("custom_value")
342 );
343 }
344
345 #[test]
346 fn test_suggestion_metadata_minimal() {
347 let json = r#"{}"#;
348
349 let metadata: SuggestionMetadata = serde_json::from_str(json).unwrap();
350 assert_eq!(metadata.source, None);
351 assert_eq!(metadata.priority, None);
352 assert!(metadata.extra.is_empty());
353 }
354
355 #[test]
356 fn test_suggestion_metadata_roundtrip() {
357 let mut extra = std::collections::HashMap::new();
358 extra.insert("key".to_string(), serde_json::json!("value"));
359
360 let metadata = SuggestionMetadata {
361 source: Some("model".to_string()),
362 priority: Some(10),
363 category: None,
364 extra,
365 };
366
367 let json = serde_json::to_string(&metadata).unwrap();
368 let parsed: SuggestionMetadata = serde_json::from_str(&json).unwrap();
369 assert_eq!(parsed, metadata);
370 }
371
372 #[test]
373 fn test_status_details_parsing() {
374 let json = r#"{
375 "error": "Connection failed",
376 "reason": "timeout",
377 "description": "The server did not respond in time",
378 "retry_count": 3
379 }"#;
380
381 let details: StatusDetails = serde_json::from_str(json).unwrap();
382 assert_eq!(details.error, Some("Connection failed".to_string()));
383 assert_eq!(details.reason, Some("timeout".to_string()));
384 assert_eq!(
385 details.description,
386 Some("The server did not respond in time".to_string())
387 );
388 assert_eq!(
389 details.extra.get("retry_count").unwrap(),
390 &serde_json::json!(3)
391 );
392 }
393
394 #[test]
395 fn test_status_details_minimal() {
396 let json = r#"{}"#;
397
398 let details: StatusDetails = serde_json::from_str(json).unwrap();
399 assert_eq!(details.error, None);
400 assert_eq!(details.reason, None);
401 assert!(details.extra.is_empty());
402 }
403
404 #[test]
405 fn test_status_details_roundtrip() {
406 let details = StatusDetails {
407 error: Some("Error message".to_string()),
408 reason: None,
409 description: Some("Description".to_string()),
410 extra: std::collections::HashMap::new(),
411 };
412
413 let json = serde_json::to_string(&details).unwrap();
414 let parsed: StatusDetails = serde_json::from_str(&json).unwrap();
415 assert_eq!(parsed, details);
416 }
417
418 #[test]
419 fn test_completion_suggestion_with_metadata() {
420 let json = r#"{
421 "text": "git status",
422 "description": "Show repository status",
423 "score": 0.95,
424 "metadata": {
425 "source": "history",
426 "priority": 1
427 }
428 }"#;
429
430 let suggestion: CompletionSuggestion = serde_json::from_str(json).unwrap();
431 assert_eq!(suggestion.text, "git status");
432 assert_eq!(
433 suggestion.description,
434 Some("Show repository status".to_string())
435 );
436 assert_eq!(suggestion.score, Some(0.95));
437 assert!(suggestion.metadata.is_some());
438
439 let meta = suggestion.metadata.unwrap();
440 assert_eq!(meta.source, Some("history".to_string()));
441 assert_eq!(meta.priority, Some(1));
442 }
443
444 #[test]
445 fn test_status_response_with_details() {
446 let json = r#"{
447 "status": "in_progress",
448 "progress": {
449 "current": 50,
450 "total": 100,
451 "percentage": 0.5
452 },
453 "details": {
454 "reason": "processing",
455 "description": "Processing request"
456 }
457 }"#;
458
459 let response: StatusResponse = serde_json::from_str(json).unwrap();
460 assert!(matches!(response.status, Status::InProgress));
461 assert!(response.progress.is_some());
462 assert!(response.details.is_some());
463
464 let details = response.details.unwrap();
465 assert_eq!(details.reason, Some("processing".to_string()));
466 }
467}