1use serde::{Deserialize, Serialize};
34use serde_json::{Value, json};
35
36use super::truncation::TruncationLimits;
37
38#[derive(Debug, Clone, Default, Serialize, Deserialize)]
43pub struct ResponseMetadata {
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub truncated: Option<bool>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub original_size: Option<usize>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub final_size: Option<usize>,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub item_count: Option<usize>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub total_items: Option<usize>,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub compressed: Option<bool>,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub retrieval_ref: Option<String>,
65}
66
67impl ResponseMetadata {
68 pub fn truncated(original_size: usize, final_size: usize) -> Self {
70 Self {
71 truncated: Some(true),
72 original_size: Some(original_size),
73 final_size: Some(final_size),
74 ..Default::default()
75 }
76 }
77
78 pub fn for_list(item_count: usize, total_items: usize) -> Self {
80 Self {
81 item_count: Some(item_count),
82 total_items: Some(total_items),
83 truncated: Some(item_count < total_items),
84 ..Default::default()
85 }
86 }
87
88 pub fn compressed(retrieval_ref: String, original_size: usize) -> Self {
90 Self {
91 compressed: Some(true),
92 retrieval_ref: Some(retrieval_ref),
93 original_size: Some(original_size),
94 ..Default::default()
95 }
96 }
97
98 pub fn is_modified(&self) -> bool {
100 self.truncated.unwrap_or(false) || self.compressed.unwrap_or(false)
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct ToolResponse {
110 pub success: bool,
112 #[serde(flatten)]
114 pub data: Value,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub metadata: Option<ResponseMetadata>,
118}
119
120impl ToolResponse {
121 pub fn success(data: Value) -> Self {
123 Self {
124 success: true,
125 data,
126 metadata: None,
127 }
128 }
129
130 pub fn success_with_metadata(data: Value, metadata: ResponseMetadata) -> Self {
132 Self {
133 success: true,
134 data,
135 metadata: Some(metadata),
136 }
137 }
138
139 pub fn to_json(&self) -> String {
141 serde_json::to_string_pretty(self).unwrap_or_else(|_| {
142 r#"{"success": false, "error": "Failed to serialize response"}"#.to_string()
143 })
144 }
145}
146
147pub fn format_success<T: Serialize>(tool_name: &str, data: &T) -> String {
160 let value = serde_json::to_value(data).unwrap_or_else(|e| {
161 json!({
162 "error": true,
163 "tool": tool_name,
164 "message": format!("Failed to serialize response: {}", e)
165 })
166 });
167
168 let response = ToolResponse::success(value);
169 response.to_json()
170}
171
172pub fn format_success_with_metadata<T: Serialize>(
183 tool_name: &str,
184 data: &T,
185 metadata: ResponseMetadata,
186) -> String {
187 let value = serde_json::to_value(data).unwrap_or_else(|e| {
188 json!({
189 "error": true,
190 "tool": tool_name,
191 "message": format!("Failed to serialize response: {}", e)
192 })
193 });
194
195 let response = ToolResponse::success_with_metadata(value, metadata);
196 response.to_json()
197}
198
199pub fn format_file_content(
212 path: &str,
213 content: &str,
214 total_lines: usize,
215 returned_lines: usize,
216 truncated: bool,
217) -> String {
218 let data = json!({
219 "file": path,
220 "total_lines": total_lines,
221 "lines_returned": returned_lines,
222 "truncated": truncated,
223 "content": content
224 });
225
226 serde_json::to_string_pretty(&data).unwrap_or_else(|_| {
227 format!(
228 r#"{{"file": "{}", "error": "Failed to serialize content"}}"#,
229 path
230 )
231 })
232}
233
234pub fn format_file_content_range(
238 path: &str,
239 content: &str,
240 start_line: usize,
241 end_line: usize,
242 total_lines: usize,
243) -> String {
244 let data = json!({
245 "file": path,
246 "lines": format!("{}-{}", start_line, end_line),
247 "total_lines": total_lines,
248 "content": content
249 });
250
251 serde_json::to_string_pretty(&data).unwrap_or_else(|_| {
252 format!(
253 r#"{{"file": "{}", "error": "Failed to serialize content"}}"#,
254 path
255 )
256 })
257}
258
259pub fn format_list(path: &str, entries: &[Value], total_count: usize, truncated: bool) -> String {
271 let data = if truncated {
272 let limits = TruncationLimits::default();
273 json!({
274 "path": path,
275 "entries": entries,
276 "entries_returned": entries.len(),
277 "total_count": total_count,
278 "truncated": true,
279 "note": format!(
280 "Showing first {} of {} entries. Use a more specific path to see others.",
281 entries.len().min(limits.max_dir_entries),
282 total_count
283 )
284 })
285 } else {
286 json!({
287 "path": path,
288 "entries": entries,
289 "total_count": total_count
290 })
291 };
292
293 serde_json::to_string_pretty(&data).unwrap_or_else(|_| {
294 format!(
295 r#"{{"path": "{}", "error": "Failed to serialize entries"}}"#,
296 path
297 )
298 })
299}
300
301pub fn format_list_with_metadata(
305 entries: &[Value],
306 metadata: ResponseMetadata,
307 extra_fields: &[(&str, Value)],
308) -> String {
309 let mut data = json!({
310 "entries": entries,
311 });
312
313 if let Some(obj) = data.as_object_mut() {
315 for (key, value) in extra_fields {
316 obj.insert((*key).to_string(), value.clone());
317 }
318
319 if let Some(truncated) = metadata.truncated {
321 obj.insert("truncated".to_string(), json!(truncated));
322 }
323 if let Some(total) = metadata.total_items {
324 obj.insert("total_count".to_string(), json!(total));
325 }
326 if let Some(count) = metadata.item_count {
327 obj.insert("entries_returned".to_string(), json!(count));
328 }
329 }
330
331 serde_json::to_string_pretty(&data)
332 .unwrap_or_else(|_| r#"{"error": "Failed to serialize list response"}"#.to_string())
333}
334
335pub fn format_write_success(
339 path: &str,
340 action: &str,
341 lines_written: usize,
342 bytes_written: usize,
343) -> String {
344 let data = json!({
345 "success": true,
346 "action": action,
347 "path": path,
348 "lines_written": lines_written,
349 "bytes_written": bytes_written
350 });
351
352 serde_json::to_string_pretty(&data).unwrap_or_else(|_| {
353 format!(
354 r#"{{"success": true, "action": "{}", "path": "{}"}}"#,
355 action, path
356 )
357 })
358}
359
360pub fn format_cancelled(path: &str, reason: &str, feedback: Option<&str>) -> String {
364 let mut data = json!({
365 "cancelled": true,
366 "STOP": "User has rejected this operation. Do NOT create this file or any alternative files.",
367 "reason": reason,
368 "original_path": path,
369 "action_required": "Stop creating files. Ask the user what they want instead."
370 });
371
372 if let Some(fb) = feedback {
373 data["user_feedback"] = json!(fb);
374 data["STOP"] =
375 json!("Do NOT create this file or any similar files. Wait for user instruction.");
376 data["action_required"] = json!(
377 "Read the user_feedback and respond accordingly. Do NOT try to create alternative files."
378 );
379 }
380
381 serde_json::to_string_pretty(&data)
382 .unwrap_or_else(|_| format!(r#"{{"cancelled": true, "reason": "{}"}}"#, reason))
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388
389 #[test]
390 fn test_response_metadata_truncated() {
391 let meta = ResponseMetadata::truncated(1000, 500);
392 assert_eq!(meta.truncated, Some(true));
393 assert_eq!(meta.original_size, Some(1000));
394 assert_eq!(meta.final_size, Some(500));
395 assert!(meta.is_modified());
396 }
397
398 #[test]
399 fn test_response_metadata_for_list() {
400 let meta = ResponseMetadata::for_list(10, 100);
401 assert_eq!(meta.item_count, Some(10));
402 assert_eq!(meta.total_items, Some(100));
403 assert_eq!(meta.truncated, Some(true));
404 }
405
406 #[test]
407 fn test_response_metadata_compressed() {
408 let meta = ResponseMetadata::compressed("ref-123".to_string(), 50000);
409 assert_eq!(meta.compressed, Some(true));
410 assert_eq!(meta.retrieval_ref, Some("ref-123".to_string()));
411 assert!(meta.is_modified());
412 }
413
414 #[test]
415 fn test_format_file_content() {
416 let response = format_file_content("test.rs", "fn main() {}", 10, 10, false);
417 let parsed: Value = serde_json::from_str(&response).unwrap();
418
419 assert_eq!(parsed["file"], "test.rs");
420 assert_eq!(parsed["total_lines"], 10);
421 assert_eq!(parsed["lines_returned"], 10);
422 assert_eq!(parsed["truncated"], false);
423 assert_eq!(parsed["content"], "fn main() {}");
424 }
425
426 #[test]
427 fn test_format_file_content_truncated() {
428 let response = format_file_content("large.rs", "content...", 5000, 2000, true);
429 let parsed: Value = serde_json::from_str(&response).unwrap();
430
431 assert_eq!(parsed["truncated"], true);
432 assert_eq!(parsed["total_lines"], 5000);
433 assert_eq!(parsed["lines_returned"], 2000);
434 }
435
436 #[test]
437 fn test_format_list() {
438 let entries = vec![
439 json!({"name": "file1.rs", "type": "file"}),
440 json!({"name": "file2.rs", "type": "file"}),
441 ];
442
443 let response = format_list("src/", &entries, 2, false);
444 let parsed: Value = serde_json::from_str(&response).unwrap();
445
446 assert_eq!(parsed["path"], "src/");
447 assert_eq!(parsed["total_count"], 2);
448 assert!(parsed["entries"].is_array());
449 assert!(parsed.get("truncated").is_none());
451 }
452
453 #[test]
454 fn test_format_list_truncated() {
455 let entries: Vec<Value> = (0..10)
456 .map(|i| json!({"name": format!("file{}.rs", i)}))
457 .collect();
458
459 let response = format_list("src/", &entries, 100, true);
460 let parsed: Value = serde_json::from_str(&response).unwrap();
461
462 assert_eq!(parsed["truncated"], true);
463 assert_eq!(parsed["total_count"], 100);
464 assert_eq!(parsed["entries_returned"], 10);
465 assert!(parsed["note"].as_str().unwrap().contains("100 entries"));
466 }
467
468 #[test]
469 fn test_format_write_success() {
470 let response = format_write_success("Dockerfile", "Created", 25, 500);
471 let parsed: Value = serde_json::from_str(&response).unwrap();
472
473 assert_eq!(parsed["success"], true);
474 assert_eq!(parsed["action"], "Created");
475 assert_eq!(parsed["path"], "Dockerfile");
476 assert_eq!(parsed["lines_written"], 25);
477 assert_eq!(parsed["bytes_written"], 500);
478 }
479
480 #[test]
481 fn test_format_cancelled() {
482 let response = format_cancelled("test.txt", "User cancelled the operation", None);
483 let parsed: Value = serde_json::from_str(&response).unwrap();
484
485 assert_eq!(parsed["cancelled"], true);
486 assert!(parsed["STOP"].as_str().unwrap().contains("rejected"));
487 }
488
489 #[test]
490 fn test_format_cancelled_with_feedback() {
491 let response = format_cancelled(
492 "test.txt",
493 "User requested changes",
494 Some("Please add comments"),
495 );
496 let parsed: Value = serde_json::from_str(&response).unwrap();
497
498 assert_eq!(parsed["cancelled"], true);
499 assert_eq!(parsed["user_feedback"], "Please add comments");
500 assert!(
501 parsed["action_required"]
502 .as_str()
503 .unwrap()
504 .contains("user_feedback")
505 );
506 }
507
508 #[test]
509 fn test_tool_response_success() {
510 let data = json!({"message": "Operation completed"});
511 let response = ToolResponse::success(data);
512
513 assert!(response.success);
514 assert!(response.metadata.is_none());
515
516 let json = response.to_json();
517 let parsed: Value = serde_json::from_str(&json).unwrap();
518 assert_eq!(parsed["success"], true);
519 assert_eq!(parsed["message"], "Operation completed");
520 }
521
522 #[test]
523 fn test_tool_response_with_metadata() {
524 let data = json!({"items": [1, 2, 3]});
525 let metadata = ResponseMetadata::for_list(3, 100);
526 let response = ToolResponse::success_with_metadata(data, metadata);
527
528 assert!(response.success);
529 assert!(response.metadata.is_some());
530
531 let json = response.to_json();
532 let parsed: Value = serde_json::from_str(&json).unwrap();
533 assert_eq!(parsed["success"], true);
534 assert_eq!(parsed["metadata"]["truncated"], true);
535 }
536}