1use serde::{Deserialize, Serialize};
16use serde_json::Value;
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
27#[serde(tag = "type", rename_all = "snake_case")]
28pub enum RpcCommand {
29 Prompt {
31 #[serde(default)]
32 id: Option<String>,
33 prompt: String,
35 #[serde(default)]
37 backend: Option<String>,
38 #[serde(default)]
40 max_iterations: Option<u32>,
41 },
42
43 Guidance {
46 #[serde(default)]
47 id: Option<String>,
48 message: String,
50 },
51
52 Steer {
55 #[serde(default)]
56 id: Option<String>,
57 message: String,
59 },
60
61 FollowUp {
63 #[serde(default)]
64 id: Option<String>,
65 message: String,
67 },
68
69 Abort {
71 #[serde(default)]
72 id: Option<String>,
73 #[serde(default)]
75 reason: Option<String>,
76 },
77
78 GetState {
80 #[serde(default)]
81 id: Option<String>,
82 },
83
84 GetIterations {
86 #[serde(default)]
87 id: Option<String>,
88 #[serde(default)]
90 include_content: bool,
91 },
92
93 SetHat {
95 #[serde(default)]
96 id: Option<String>,
97 hat: String,
99 },
100
101 ExtensionUiResponse {
103 #[serde(default)]
104 id: Option<String>,
105 request_id: String,
107 response: Value,
109 },
110}
111
112impl RpcCommand {
113 pub fn id(&self) -> Option<&str> {
115 match self {
116 RpcCommand::Prompt { id, .. } => id.as_deref(),
117 RpcCommand::Guidance { id, .. } => id.as_deref(),
118 RpcCommand::Steer { id, .. } => id.as_deref(),
119 RpcCommand::FollowUp { id, .. } => id.as_deref(),
120 RpcCommand::Abort { id, .. } => id.as_deref(),
121 RpcCommand::GetState { id } => id.as_deref(),
122 RpcCommand::GetIterations { id, .. } => id.as_deref(),
123 RpcCommand::SetHat { id, .. } => id.as_deref(),
124 RpcCommand::ExtensionUiResponse { id, .. } => id.as_deref(),
125 }
126 }
127
128 pub fn command_type(&self) -> &'static str {
130 match self {
131 RpcCommand::Prompt { .. } => "prompt",
132 RpcCommand::Guidance { .. } => "guidance",
133 RpcCommand::Steer { .. } => "steer",
134 RpcCommand::FollowUp { .. } => "follow_up",
135 RpcCommand::Abort { .. } => "abort",
136 RpcCommand::GetState { .. } => "get_state",
137 RpcCommand::GetIterations { .. } => "get_iterations",
138 RpcCommand::SetHat { .. } => "set_hat",
139 RpcCommand::ExtensionUiResponse { .. } => "extension_ui_response",
140 }
141 }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
153#[serde(tag = "type", rename_all = "snake_case")]
154pub enum RpcEvent {
155 LoopStarted {
157 prompt: String,
159 max_iterations: Option<u32>,
161 backend: String,
163 started_at: u64,
165 },
166
167 IterationStart {
169 iteration: u32,
171 max_iterations: Option<u32>,
173 hat: String,
175 hat_display: String,
177 backend: String,
179 started_at: u64,
181 },
182
183 IterationEnd {
185 iteration: u32,
187 duration_ms: u64,
189 cost_usd: f64,
191 input_tokens: u64,
193 output_tokens: u64,
195 cache_read_tokens: u64,
197 cache_write_tokens: u64,
199 loop_complete_triggered: bool,
201 },
202
203 TextDelta {
205 iteration: u32,
207 delta: String,
209 },
210
211 ToolCallStart {
213 iteration: u32,
215 tool_name: String,
217 tool_call_id: String,
219 input: Value,
221 },
222
223 ToolCallEnd {
225 iteration: u32,
227 tool_call_id: String,
229 output: String,
231 is_error: bool,
233 duration_ms: u64,
235 },
236
237 Error {
239 iteration: u32,
241 code: String,
243 message: String,
245 recoverable: bool,
247 },
248
249 HatChanged {
251 iteration: u32,
253 from_hat: String,
255 to_hat: String,
257 to_hat_display: String,
259 reason: String,
261 },
262
263 TaskStatusChanged {
265 task_id: String,
267 from_status: String,
269 to_status: String,
271 title: String,
273 },
274
275 TaskCountsUpdated {
277 total: usize,
279 open: usize,
281 closed: usize,
283 ready: usize,
285 },
286
287 GuidanceAck {
289 message: String,
291 applies_to: GuidanceTarget,
293 },
294
295 LoopTerminated {
297 reason: TerminationReason,
299 total_iterations: u32,
301 duration_ms: u64,
303 total_cost_usd: f64,
305 terminated_at: u64,
307 },
308
309 Response {
311 command: String,
313 #[serde(skip_serializing_if = "Option::is_none")]
315 id: Option<String>,
316 success: bool,
318 #[serde(skip_serializing_if = "Option::is_none")]
320 data: Option<Value>,
321 #[serde(skip_serializing_if = "Option::is_none")]
323 error: Option<String>,
324 },
325
326 OrchestrationEvent {
329 topic: String,
331 payload: String,
333 #[serde(skip_serializing_if = "Option::is_none")]
335 source: Option<String>,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 target: Option<String>,
339 },
340
341 WaveStarted {
343 hat_name: String,
345 worker_count: u32,
347 timeout_secs: u64,
349 },
350
351 WaveWorkerDone {
353 index: u32,
355 total: u32,
357 duration_ms: u64,
359 success: bool,
361 payload_preview: String,
363 },
364
365 WaveWorkerTextDelta {
367 worker_index: u32,
369 delta: String,
371 },
372
373 WaveCompleted {
375 succeeded: usize,
377 failed: usize,
379 duration_ms: u64,
381 },
382}
383
384impl RpcEvent {
385 pub fn success_response(command: &str, id: Option<String>, data: Option<Value>) -> Self {
387 RpcEvent::Response {
388 command: command.to_string(),
389 id,
390 success: true,
391 data,
392 error: None,
393 }
394 }
395
396 pub fn error_response(command: &str, id: Option<String>, error: impl Into<String>) -> Self {
398 RpcEvent::Response {
399 command: command.to_string(),
400 id,
401 success: false,
402 data: None,
403 error: Some(error.into()),
404 }
405 }
406}
407
408#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
414#[serde(rename_all = "snake_case")]
415pub enum GuidanceTarget {
416 Current,
418 Next,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
424#[serde(rename_all = "snake_case")]
425pub enum TerminationReason {
426 Completed,
428 MaxIterations,
430 Interrupted,
432 Error,
434 AllTasksClosed,
436 BackpressureLimit,
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
442pub struct RpcState {
443 pub iteration: u32,
445 pub max_iterations: Option<u32>,
447 pub hat: String,
449 pub hat_display: String,
451 pub backend: String,
453 pub completed: bool,
455 pub started_at: u64,
457 pub iteration_started_at: Option<u64>,
459 pub task_counts: RpcTaskCounts,
461 pub active_task: Option<RpcTaskSummary>,
463 pub total_cost_usd: f64,
465}
466
467#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
469pub struct RpcTaskCounts {
470 pub total: usize,
471 pub open: usize,
472 pub closed: usize,
473 pub ready: usize,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
478pub struct RpcTaskSummary {
479 pub id: String,
480 pub title: String,
481 pub status: String,
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
486pub struct RpcIterationInfo {
487 pub iteration: u32,
489 pub hat: String,
491 pub backend: String,
493 pub duration_ms: u64,
495 pub cost_usd: f64,
497 pub loop_complete_triggered: bool,
499 #[serde(skip_serializing_if = "Option::is_none")]
501 pub content: Option<String>,
502}
503
504pub fn parse_command(line: &str) -> Result<RpcCommand, String> {
512 let trimmed = line.trim();
513 if trimmed.is_empty() {
514 return Err("empty line".to_string());
515 }
516 serde_json::from_str(trimmed).map_err(|e| format!("JSON parse error: {e}"))
517}
518
519pub fn emit_event(event: &RpcEvent) -> String {
521 serde_json::to_string(event).expect("RpcEvent serialization failed")
523}
524
525pub fn emit_event_line(event: &RpcEvent) -> String {
527 let mut line = emit_event(event);
528 line.push('\n');
529 line
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535 use serde_json::json;
536
537 #[test]
542 fn test_prompt_command_roundtrip() {
543 let cmd = RpcCommand::Prompt {
544 id: Some("req-1".to_string()),
545 prompt: "implement feature X".to_string(),
546 backend: Some("claude".to_string()),
547 max_iterations: Some(5),
548 };
549 let json = serde_json::to_string(&cmd).unwrap();
550 let parsed: RpcCommand = serde_json::from_str(&json).unwrap();
551 assert_eq!(cmd, parsed);
552 }
553
554 #[test]
555 fn test_guidance_command_roundtrip() {
556 let cmd = RpcCommand::Guidance {
557 id: None,
558 message: "focus on tests".to_string(),
559 };
560 let json = serde_json::to_string(&cmd).unwrap();
561 let parsed: RpcCommand = serde_json::from_str(&json).unwrap();
562 assert_eq!(cmd, parsed);
563 }
564
565 #[test]
566 fn test_steer_command_roundtrip() {
567 let cmd = RpcCommand::Steer {
568 id: Some("steer-1".to_string()),
569 message: "use async instead".to_string(),
570 };
571 let json = serde_json::to_string(&cmd).unwrap();
572 let parsed: RpcCommand = serde_json::from_str(&json).unwrap();
573 assert_eq!(cmd, parsed);
574 }
575
576 #[test]
577 fn test_follow_up_command_roundtrip() {
578 let cmd = RpcCommand::FollowUp {
579 id: None,
580 message: "now run the tests".to_string(),
581 };
582 let json = serde_json::to_string(&cmd).unwrap();
583 let parsed: RpcCommand = serde_json::from_str(&json).unwrap();
584 assert_eq!(cmd, parsed);
585 }
586
587 #[test]
588 fn test_abort_command_roundtrip() {
589 let cmd = RpcCommand::Abort {
590 id: Some("abort-1".to_string()),
591 reason: Some("user cancelled".to_string()),
592 };
593 let json = serde_json::to_string(&cmd).unwrap();
594 let parsed: RpcCommand = serde_json::from_str(&json).unwrap();
595 assert_eq!(cmd, parsed);
596 }
597
598 #[test]
599 fn test_get_state_command_roundtrip() {
600 let cmd = RpcCommand::GetState {
601 id: Some("state-1".to_string()),
602 };
603 let json = serde_json::to_string(&cmd).unwrap();
604 let parsed: RpcCommand = serde_json::from_str(&json).unwrap();
605 assert_eq!(cmd, parsed);
606 }
607
608 #[test]
609 fn test_get_iterations_command_roundtrip() {
610 let cmd = RpcCommand::GetIterations {
611 id: Some("iters-1".to_string()),
612 include_content: true,
613 };
614 let json = serde_json::to_string(&cmd).unwrap();
615 let parsed: RpcCommand = serde_json::from_str(&json).unwrap();
616 assert_eq!(cmd, parsed);
617 }
618
619 #[test]
620 fn test_set_hat_command_roundtrip() {
621 let cmd = RpcCommand::SetHat {
622 id: None,
623 hat: "confessor".to_string(),
624 };
625 let json = serde_json::to_string(&cmd).unwrap();
626 let parsed: RpcCommand = serde_json::from_str(&json).unwrap();
627 assert_eq!(cmd, parsed);
628 }
629
630 #[test]
631 fn test_extension_ui_response_command_roundtrip() {
632 let cmd = RpcCommand::ExtensionUiResponse {
633 id: Some("ext-1".to_string()),
634 request_id: "ui-req-123".to_string(),
635 response: json!({"selected": "option-a"}),
636 };
637 let json = serde_json::to_string(&cmd).unwrap();
638 let parsed: RpcCommand = serde_json::from_str(&json).unwrap();
639 assert_eq!(cmd, parsed);
640 }
641
642 #[test]
647 fn test_loop_started_event_roundtrip() {
648 let event = RpcEvent::LoopStarted {
649 prompt: "test prompt".to_string(),
650 max_iterations: Some(10),
651 backend: "claude".to_string(),
652 started_at: 1_700_000_000_000,
653 };
654 let json = serde_json::to_string(&event).unwrap();
655 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
656 assert_eq!(event, parsed);
657 }
658
659 #[test]
660 fn test_iteration_start_event_roundtrip() {
661 let event = RpcEvent::IterationStart {
662 iteration: 3,
663 max_iterations: Some(10),
664 hat: "builder".to_string(),
665 hat_display: "🔨Builder".to_string(),
666 backend: "claude".to_string(),
667 started_at: 1_700_000_001_000,
668 };
669 let json = serde_json::to_string(&event).unwrap();
670 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
671 assert_eq!(event, parsed);
672 }
673
674 #[test]
675 fn test_iteration_end_event_roundtrip() {
676 let event = RpcEvent::IterationEnd {
677 iteration: 3,
678 duration_ms: 5432,
679 cost_usd: 0.0054,
680 input_tokens: 8000,
681 output_tokens: 500,
682 cache_read_tokens: 7500,
683 cache_write_tokens: 100,
684 loop_complete_triggered: false,
685 };
686 let json = serde_json::to_string(&event).unwrap();
687 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
688 assert_eq!(event, parsed);
689 }
690
691 #[test]
692 fn test_text_delta_event_roundtrip() {
693 let event = RpcEvent::TextDelta {
694 iteration: 2,
695 delta: "Hello, world!".to_string(),
696 };
697 let json = serde_json::to_string(&event).unwrap();
698 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
699 assert_eq!(event, parsed);
700 }
701
702 #[test]
703 fn test_tool_call_start_event_roundtrip() {
704 let event = RpcEvent::ToolCallStart {
705 iteration: 1,
706 tool_name: "Bash".to_string(),
707 tool_call_id: "toolu_123".to_string(),
708 input: json!({"command": "ls -la"}),
709 };
710 let json = serde_json::to_string(&event).unwrap();
711 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
712 assert_eq!(event, parsed);
713 }
714
715 #[test]
716 fn test_tool_call_end_event_roundtrip() {
717 let event = RpcEvent::ToolCallEnd {
718 iteration: 1,
719 tool_call_id: "toolu_123".to_string(),
720 output: "file1.rs\nfile2.rs".to_string(),
721 is_error: false,
722 duration_ms: 150,
723 };
724 let json = serde_json::to_string(&event).unwrap();
725 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
726 assert_eq!(event, parsed);
727 }
728
729 #[test]
730 fn test_error_event_roundtrip() {
731 let event = RpcEvent::Error {
732 iteration: 2,
733 code: "TIMEOUT".to_string(),
734 message: "API request timed out".to_string(),
735 recoverable: true,
736 };
737 let json = serde_json::to_string(&event).unwrap();
738 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
739 assert_eq!(event, parsed);
740 }
741
742 #[test]
743 fn test_hat_changed_event_roundtrip() {
744 let event = RpcEvent::HatChanged {
745 iteration: 4,
746 from_hat: "builder".to_string(),
747 to_hat: "confessor".to_string(),
748 to_hat_display: "🙏Confessor".to_string(),
749 reason: "build.done received".to_string(),
750 };
751 let json = serde_json::to_string(&event).unwrap();
752 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
753 assert_eq!(event, parsed);
754 }
755
756 #[test]
757 fn test_task_status_changed_event_roundtrip() {
758 let event = RpcEvent::TaskStatusChanged {
759 task_id: "task-123".to_string(),
760 from_status: "open".to_string(),
761 to_status: "closed".to_string(),
762 title: "Implement feature X".to_string(),
763 };
764 let json = serde_json::to_string(&event).unwrap();
765 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
766 assert_eq!(event, parsed);
767 }
768
769 #[test]
770 fn test_task_counts_updated_event_roundtrip() {
771 let event = RpcEvent::TaskCountsUpdated {
772 total: 10,
773 open: 3,
774 closed: 7,
775 ready: 2,
776 };
777 let json = serde_json::to_string(&event).unwrap();
778 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
779 assert_eq!(event, parsed);
780 }
781
782 #[test]
783 fn test_guidance_ack_event_roundtrip() {
784 let event = RpcEvent::GuidanceAck {
785 message: "focus on tests".to_string(),
786 applies_to: GuidanceTarget::Next,
787 };
788 let json = serde_json::to_string(&event).unwrap();
789 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
790 assert_eq!(event, parsed);
791 }
792
793 #[test]
794 fn test_loop_terminated_event_roundtrip() {
795 let event = RpcEvent::LoopTerminated {
796 reason: TerminationReason::Completed,
797 total_iterations: 5,
798 duration_ms: 120_000,
799 total_cost_usd: 0.25,
800 terminated_at: 1_700_000_120_000,
801 };
802 let json = serde_json::to_string(&event).unwrap();
803 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
804 assert_eq!(event, parsed);
805 }
806
807 #[test]
808 fn test_response_event_success_roundtrip() {
809 let event = RpcEvent::Response {
810 command: "get_state".to_string(),
811 id: Some("req-42".to_string()),
812 success: true,
813 data: Some(json!({"iteration": 3})),
814 error: None,
815 };
816 let json = serde_json::to_string(&event).unwrap();
817 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
818 assert_eq!(event, parsed);
819 }
820
821 #[test]
822 fn test_response_event_error_roundtrip() {
823 let event = RpcEvent::Response {
824 command: "prompt".to_string(),
825 id: Some("req-43".to_string()),
826 success: false,
827 data: None,
828 error: Some("loop already running".to_string()),
829 };
830 let json = serde_json::to_string(&event).unwrap();
831 let parsed: RpcEvent = serde_json::from_str(&json).unwrap();
832 assert_eq!(event, parsed);
833 }
834
835 #[test]
840 fn test_termination_reason_variants() {
841 let reasons = [
842 TerminationReason::Completed,
843 TerminationReason::MaxIterations,
844 TerminationReason::Interrupted,
845 TerminationReason::Error,
846 TerminationReason::AllTasksClosed,
847 TerminationReason::BackpressureLimit,
848 ];
849 for reason in reasons {
850 let json = serde_json::to_string(&reason).unwrap();
851 let parsed: TerminationReason = serde_json::from_str(&json).unwrap();
852 assert_eq!(reason, parsed);
853 }
854 }
855
856 #[test]
861 fn test_parse_command_valid() {
862 let line = r#"{"type": "get_state", "id": "test-1"}"#;
863 let cmd = parse_command(line).unwrap();
864 assert!(matches!(cmd, RpcCommand::GetState { id: Some(ref i) } if i == "test-1"));
865 }
866
867 #[test]
868 fn test_parse_command_empty() {
869 assert!(parse_command("").is_err());
870 assert!(parse_command(" ").is_err());
871 }
872
873 #[test]
874 fn test_parse_command_invalid_json() {
875 assert!(parse_command("{not valid}").is_err());
876 }
877
878 #[test]
879 fn test_parse_command_unknown_type() {
880 let line = r#"{"type": "unknown_command"}"#;
881 assert!(parse_command(line).is_err());
882 }
883
884 #[test]
885 fn test_emit_event() {
886 let event = RpcEvent::TextDelta {
887 iteration: 1,
888 delta: "hello".to_string(),
889 };
890 let json = emit_event(&event);
891 assert!(!json.ends_with('\n'));
892 assert!(json.contains(r#""type":"text_delta""#));
893 }
894
895 #[test]
896 fn test_emit_event_line() {
897 let event = RpcEvent::TextDelta {
898 iteration: 1,
899 delta: "hello".to_string(),
900 };
901 let line = emit_event_line(&event);
902 assert!(line.ends_with('\n'));
903 assert_eq!(line.matches('\n').count(), 1);
904 }
905
906 #[test]
911 fn test_command_id() {
912 let cmd = RpcCommand::GetState {
913 id: Some("req-1".to_string()),
914 };
915 assert_eq!(cmd.id(), Some("req-1"));
916
917 let cmd = RpcCommand::Abort {
918 id: None,
919 reason: None,
920 };
921 assert_eq!(cmd.id(), None);
922 }
923
924 #[test]
925 fn test_command_type() {
926 assert_eq!(
927 RpcCommand::Prompt {
928 id: None,
929 prompt: "test".into(),
930 backend: None,
931 max_iterations: None
932 }
933 .command_type(),
934 "prompt"
935 );
936 assert_eq!(
937 RpcCommand::GetState { id: None }.command_type(),
938 "get_state"
939 );
940 assert_eq!(
941 RpcCommand::Abort {
942 id: None,
943 reason: None
944 }
945 .command_type(),
946 "abort"
947 );
948 }
949
950 #[test]
951 fn test_success_response() {
952 let event = RpcEvent::success_response(
953 "get_state",
954 Some("req-1".into()),
955 Some(json!({"ok": true})),
956 );
957 match event {
958 RpcEvent::Response {
959 command,
960 id,
961 success,
962 data,
963 error,
964 } => {
965 assert_eq!(command, "get_state");
966 assert_eq!(id, Some("req-1".to_string()));
967 assert!(success);
968 assert!(data.is_some());
969 assert!(error.is_none());
970 }
971 _ => panic!("Expected Response event"),
972 }
973 }
974
975 #[test]
976 fn test_error_response() {
977 let event = RpcEvent::error_response("prompt", None, "loop already running");
978 match event {
979 RpcEvent::Response {
980 command,
981 id,
982 success,
983 data,
984 error,
985 } => {
986 assert_eq!(command, "prompt");
987 assert!(id.is_none());
988 assert!(!success);
989 assert!(data.is_none());
990 assert_eq!(error, Some("loop already running".to_string()));
991 }
992 _ => panic!("Expected Response event"),
993 }
994 }
995
996 #[test]
1001 fn test_rpc_state_roundtrip() {
1002 let state = RpcState {
1003 iteration: 3,
1004 max_iterations: Some(10),
1005 hat: "builder".to_string(),
1006 hat_display: "🔨Builder".to_string(),
1007 backend: "claude".to_string(),
1008 completed: false,
1009 started_at: 1_700_000_000_000,
1010 iteration_started_at: Some(1_700_000_005_000),
1011 task_counts: RpcTaskCounts {
1012 total: 5,
1013 open: 2,
1014 closed: 3,
1015 ready: 1,
1016 },
1017 active_task: Some(RpcTaskSummary {
1018 id: "task-123".to_string(),
1019 title: "Fix bug".to_string(),
1020 status: "running".to_string(),
1021 }),
1022 total_cost_usd: 0.15,
1023 };
1024 let json = serde_json::to_string(&state).unwrap();
1025 let parsed: RpcState = serde_json::from_str(&json).unwrap();
1026 assert_eq!(state, parsed);
1027 }
1028
1029 #[test]
1030 fn test_rpc_iteration_info_roundtrip() {
1031 let info = RpcIterationInfo {
1032 iteration: 2,
1033 hat: "builder".to_string(),
1034 backend: "claude".to_string(),
1035 duration_ms: 5000,
1036 cost_usd: 0.05,
1037 loop_complete_triggered: false,
1038 content: Some("iteration content here".to_string()),
1039 };
1040 let json = serde_json::to_string(&info).unwrap();
1041 let parsed: RpcIterationInfo = serde_json::from_str(&json).unwrap();
1042 assert_eq!(info, parsed);
1043 }
1044
1045 #[test]
1050 fn test_pi_aligned_naming() {
1051 let text_delta = RpcEvent::TextDelta {
1053 iteration: 1,
1054 delta: "test".to_string(),
1055 };
1056 let json = serde_json::to_string(&text_delta).unwrap();
1057 assert!(json.contains(r#""type":"text_delta""#));
1058
1059 let tool_start = RpcEvent::ToolCallStart {
1060 iteration: 1,
1061 tool_name: "Bash".to_string(),
1062 tool_call_id: "id".to_string(),
1063 input: json!({}),
1064 };
1065 let json = serde_json::to_string(&tool_start).unwrap();
1066 assert!(json.contains(r#""type":"tool_call_start""#));
1067
1068 let tool_end = RpcEvent::ToolCallEnd {
1069 iteration: 1,
1070 tool_call_id: "id".to_string(),
1071 output: String::new(),
1072 is_error: false,
1073 duration_ms: 0,
1074 };
1075 let json = serde_json::to_string(&tool_end).unwrap();
1076 assert!(json.contains(r#""type":"tool_call_end""#));
1077 }
1078}