1#[derive(Debug, thiserror::Error)]
6pub enum ChannelError {
7 #[error("I/O error: {0}")]
9 Io(#[from] std::io::Error),
10
11 #[error("channel closed")]
13 ChannelClosed,
14
15 #[error("confirmation cancelled")]
17 ConfirmCancelled,
18
19 #[error("{0}")]
21 Other(String),
22}
23
24#[derive(Debug)]
26pub struct ToolStartEvent<'a> {
27 pub tool_name: &'a str,
28 pub tool_call_id: &'a str,
29 pub params: Option<serde_json::Value>,
30 pub parent_tool_use_id: Option<String>,
31}
32
33#[derive(Debug)]
35pub struct ToolOutputEvent<'a> {
36 pub tool_name: &'a str,
37 pub body: &'a str,
38 pub diff: Option<crate::DiffData>,
39 pub filter_stats: Option<String>,
40 pub kept_lines: Option<Vec<usize>>,
41 pub locations: Option<Vec<String>>,
42 pub tool_call_id: &'a str,
43 pub is_error: bool,
44 pub parent_tool_use_id: Option<String>,
45 pub raw_response: Option<serde_json::Value>,
46 pub started_at: Option<std::time::Instant>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum AttachmentKind {
52 Audio,
53 Image,
54 Video,
55 File,
56}
57
58#[derive(Debug, Clone)]
60pub struct Attachment {
61 pub kind: AttachmentKind,
62 pub data: Vec<u8>,
63 pub filename: Option<String>,
64}
65
66#[derive(Debug, Clone)]
68pub struct ChannelMessage {
69 pub text: String,
70 pub attachments: Vec<Attachment>,
71}
72
73pub trait Channel: Send {
75 fn recv(&mut self)
81 -> impl Future<Output = Result<Option<ChannelMessage>, ChannelError>> + Send;
82
83 fn try_recv(&mut self) -> Option<ChannelMessage> {
85 None
86 }
87
88 fn supports_exit(&self) -> bool {
93 true
94 }
95
96 fn send(&mut self, text: &str) -> impl Future<Output = Result<(), ChannelError>> + Send;
102
103 fn send_chunk(&mut self, chunk: &str) -> impl Future<Output = Result<(), ChannelError>> + Send;
109
110 fn flush_chunks(&mut self) -> impl Future<Output = Result<(), ChannelError>> + Send;
116
117 fn send_typing(&mut self) -> impl Future<Output = Result<(), ChannelError>> + Send {
123 async { Ok(()) }
124 }
125
126 fn send_status(
132 &mut self,
133 _text: &str,
134 ) -> impl Future<Output = Result<(), ChannelError>> + Send {
135 async { Ok(()) }
136 }
137
138 fn send_thinking_chunk(
144 &mut self,
145 _chunk: &str,
146 ) -> impl Future<Output = Result<(), ChannelError>> + Send {
147 async { Ok(()) }
148 }
149
150 fn send_queue_count(
156 &mut self,
157 _count: usize,
158 ) -> impl Future<Output = Result<(), ChannelError>> + Send {
159 async { Ok(()) }
160 }
161
162 fn send_usage(
168 &mut self,
169 _input_tokens: u64,
170 _output_tokens: u64,
171 _context_window: u64,
172 ) -> impl Future<Output = Result<(), ChannelError>> + Send {
173 async { Ok(()) }
174 }
175
176 fn send_diff(
182 &mut self,
183 _diff: crate::DiffData,
184 ) -> impl Future<Output = Result<(), ChannelError>> + Send {
185 async { Ok(()) }
186 }
187
188 fn send_tool_start(
198 &mut self,
199 _event: ToolStartEvent<'_>,
200 ) -> impl Future<Output = Result<(), ChannelError>> + Send {
201 async { Ok(()) }
202 }
203
204 fn send_tool_output(
215 &mut self,
216 event: ToolOutputEvent<'_>,
217 ) -> impl Future<Output = Result<(), ChannelError>> + Send {
218 let formatted = crate::agent::format_tool_output(event.tool_name, event.body);
219 async move { self.send(&formatted).await }
220 }
221
222 fn confirm(
229 &mut self,
230 _prompt: &str,
231 ) -> impl Future<Output = Result<bool, ChannelError>> + Send {
232 async { Ok(true) }
233 }
234
235 fn send_stop_hint(
244 &mut self,
245 _hint: StopHint,
246 ) -> impl Future<Output = Result<(), ChannelError>> + Send {
247 async { Ok(()) }
248 }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257pub enum StopHint {
258 MaxTokens,
260 MaxTurnRequests,
262}
263
264#[derive(Debug, Clone)]
266pub struct ToolStartData {
267 pub tool_name: String,
268 pub tool_call_id: String,
269 pub params: Option<serde_json::Value>,
271 pub parent_tool_use_id: Option<String>,
273 pub started_at: std::time::Instant,
275}
276
277#[derive(Debug, Clone)]
279pub struct ToolOutputData {
280 pub tool_name: String,
281 pub display: String,
282 pub diff: Option<crate::DiffData>,
283 pub filter_stats: Option<String>,
284 pub kept_lines: Option<Vec<usize>>,
285 pub locations: Option<Vec<String>>,
286 pub tool_call_id: String,
287 pub is_error: bool,
288 pub terminal_id: Option<String>,
290 pub parent_tool_use_id: Option<String>,
292 pub raw_response: Option<serde_json::Value>,
294 pub started_at: Option<std::time::Instant>,
296}
297
298#[derive(Debug, Clone)]
300pub enum LoopbackEvent {
301 Chunk(String),
302 Flush,
303 FullMessage(String),
304 Status(String),
305 ToolStart(Box<ToolStartData>),
307 ToolOutput(Box<ToolOutputData>),
308 Usage {
310 input_tokens: u64,
311 output_tokens: u64,
312 context_window: u64,
313 },
314 SessionTitle(String),
316 Plan(Vec<(String, PlanItemStatus)>),
318 ThinkingChunk(String),
320 Stop(StopHint),
324}
325
326#[derive(Debug, Clone)]
328pub enum PlanItemStatus {
329 Pending,
330 InProgress,
331 Completed,
332}
333
334pub struct LoopbackHandle {
336 pub input_tx: tokio::sync::mpsc::Sender<ChannelMessage>,
337 pub output_rx: tokio::sync::mpsc::Receiver<LoopbackEvent>,
338 pub cancel_signal: std::sync::Arc<tokio::sync::Notify>,
340}
341
342pub struct LoopbackChannel {
344 input_rx: tokio::sync::mpsc::Receiver<ChannelMessage>,
345 output_tx: tokio::sync::mpsc::Sender<LoopbackEvent>,
346}
347
348impl LoopbackChannel {
349 #[must_use]
351 pub fn pair(buffer: usize) -> (Self, LoopbackHandle) {
352 let (input_tx, input_rx) = tokio::sync::mpsc::channel(buffer);
353 let (output_tx, output_rx) = tokio::sync::mpsc::channel(buffer);
354 let cancel_signal = std::sync::Arc::new(tokio::sync::Notify::new());
355 (
356 Self {
357 input_rx,
358 output_tx,
359 },
360 LoopbackHandle {
361 input_tx,
362 output_rx,
363 cancel_signal,
364 },
365 )
366 }
367}
368
369impl Channel for LoopbackChannel {
370 fn supports_exit(&self) -> bool {
371 false
372 }
373
374 async fn recv(&mut self) -> Result<Option<ChannelMessage>, ChannelError> {
375 Ok(self.input_rx.recv().await)
376 }
377
378 async fn send(&mut self, text: &str) -> Result<(), ChannelError> {
379 self.output_tx
380 .send(LoopbackEvent::FullMessage(text.to_owned()))
381 .await
382 .map_err(|_| ChannelError::ChannelClosed)
383 }
384
385 async fn send_chunk(&mut self, chunk: &str) -> Result<(), ChannelError> {
386 self.output_tx
387 .send(LoopbackEvent::Chunk(chunk.to_owned()))
388 .await
389 .map_err(|_| ChannelError::ChannelClosed)
390 }
391
392 async fn flush_chunks(&mut self) -> Result<(), ChannelError> {
393 self.output_tx
394 .send(LoopbackEvent::Flush)
395 .await
396 .map_err(|_| ChannelError::ChannelClosed)
397 }
398
399 async fn send_status(&mut self, text: &str) -> Result<(), ChannelError> {
400 self.output_tx
401 .send(LoopbackEvent::Status(text.to_owned()))
402 .await
403 .map_err(|_| ChannelError::ChannelClosed)
404 }
405
406 async fn send_thinking_chunk(&mut self, chunk: &str) -> Result<(), ChannelError> {
407 self.output_tx
408 .send(LoopbackEvent::ThinkingChunk(chunk.to_owned()))
409 .await
410 .map_err(|_| ChannelError::ChannelClosed)
411 }
412
413 async fn send_tool_start(&mut self, event: ToolStartEvent<'_>) -> Result<(), ChannelError> {
414 self.output_tx
415 .send(LoopbackEvent::ToolStart(Box::new(ToolStartData {
416 tool_name: event.tool_name.to_owned(),
417 tool_call_id: event.tool_call_id.to_owned(),
418 params: event.params,
419 parent_tool_use_id: event.parent_tool_use_id,
420 started_at: std::time::Instant::now(),
421 })))
422 .await
423 .map_err(|_| ChannelError::ChannelClosed)
424 }
425
426 async fn send_tool_output(&mut self, event: ToolOutputEvent<'_>) -> Result<(), ChannelError> {
427 self.output_tx
428 .send(LoopbackEvent::ToolOutput(Box::new(ToolOutputData {
429 tool_name: event.tool_name.to_owned(),
430 display: event.body.to_owned(),
431 diff: event.diff,
432 filter_stats: event.filter_stats,
433 kept_lines: event.kept_lines,
434 locations: event.locations,
435 tool_call_id: event.tool_call_id.to_owned(),
436 is_error: event.is_error,
437 terminal_id: None,
438 parent_tool_use_id: event.parent_tool_use_id,
439 raw_response: event.raw_response,
440 started_at: event.started_at,
441 })))
442 .await
443 .map_err(|_| ChannelError::ChannelClosed)
444 }
445
446 async fn confirm(&mut self, _prompt: &str) -> Result<bool, ChannelError> {
447 Ok(true)
448 }
449
450 async fn send_stop_hint(&mut self, hint: StopHint) -> Result<(), ChannelError> {
451 self.output_tx
452 .send(LoopbackEvent::Stop(hint))
453 .await
454 .map_err(|_| ChannelError::ChannelClosed)
455 }
456
457 async fn send_usage(
458 &mut self,
459 input_tokens: u64,
460 output_tokens: u64,
461 context_window: u64,
462 ) -> Result<(), ChannelError> {
463 self.output_tx
464 .send(LoopbackEvent::Usage {
465 input_tokens,
466 output_tokens,
467 context_window,
468 })
469 .await
470 .map_err(|_| ChannelError::ChannelClosed)
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 #[test]
479 fn channel_message_creation() {
480 let msg = ChannelMessage {
481 text: "hello".to_string(),
482 attachments: vec![],
483 };
484 assert_eq!(msg.text, "hello");
485 assert!(msg.attachments.is_empty());
486 }
487
488 struct StubChannel;
489
490 impl Channel for StubChannel {
491 async fn recv(&mut self) -> Result<Option<ChannelMessage>, ChannelError> {
492 Ok(None)
493 }
494
495 async fn send(&mut self, _text: &str) -> Result<(), ChannelError> {
496 Ok(())
497 }
498
499 async fn send_chunk(&mut self, _chunk: &str) -> Result<(), ChannelError> {
500 Ok(())
501 }
502
503 async fn flush_chunks(&mut self) -> Result<(), ChannelError> {
504 Ok(())
505 }
506 }
507
508 #[tokio::test]
509 async fn send_chunk_default_is_noop() {
510 let mut ch = StubChannel;
511 ch.send_chunk("partial").await.unwrap();
512 }
513
514 #[tokio::test]
515 async fn flush_chunks_default_is_noop() {
516 let mut ch = StubChannel;
517 ch.flush_chunks().await.unwrap();
518 }
519
520 #[tokio::test]
521 async fn stub_channel_confirm_auto_approves() {
522 let mut ch = StubChannel;
523 let result = ch.confirm("Delete everything?").await.unwrap();
524 assert!(result);
525 }
526
527 #[tokio::test]
528 async fn stub_channel_send_typing_default() {
529 let mut ch = StubChannel;
530 ch.send_typing().await.unwrap();
531 }
532
533 #[tokio::test]
534 async fn stub_channel_recv_returns_none() {
535 let mut ch = StubChannel;
536 let msg = ch.recv().await.unwrap();
537 assert!(msg.is_none());
538 }
539
540 #[tokio::test]
541 async fn stub_channel_send_ok() {
542 let mut ch = StubChannel;
543 ch.send("hello").await.unwrap();
544 }
545
546 #[test]
547 fn channel_message_clone() {
548 let msg = ChannelMessage {
549 text: "test".to_string(),
550 attachments: vec![],
551 };
552 let cloned = msg.clone();
553 assert_eq!(cloned.text, "test");
554 }
555
556 #[test]
557 fn channel_message_debug() {
558 let msg = ChannelMessage {
559 text: "debug".to_string(),
560 attachments: vec![],
561 };
562 let debug = format!("{msg:?}");
563 assert!(debug.contains("debug"));
564 }
565
566 #[test]
567 fn attachment_kind_equality() {
568 assert_eq!(AttachmentKind::Audio, AttachmentKind::Audio);
569 assert_ne!(AttachmentKind::Audio, AttachmentKind::Image);
570 }
571
572 #[test]
573 fn attachment_construction() {
574 let a = Attachment {
575 kind: AttachmentKind::Audio,
576 data: vec![0, 1, 2],
577 filename: Some("test.wav".into()),
578 };
579 assert_eq!(a.kind, AttachmentKind::Audio);
580 assert_eq!(a.data.len(), 3);
581 assert_eq!(a.filename.as_deref(), Some("test.wav"));
582 }
583
584 #[test]
585 fn channel_message_with_attachments() {
586 let msg = ChannelMessage {
587 text: String::new(),
588 attachments: vec![Attachment {
589 kind: AttachmentKind::Audio,
590 data: vec![42],
591 filename: None,
592 }],
593 };
594 assert_eq!(msg.attachments.len(), 1);
595 assert_eq!(msg.attachments[0].kind, AttachmentKind::Audio);
596 }
597
598 #[test]
599 fn stub_channel_try_recv_returns_none() {
600 let mut ch = StubChannel;
601 assert!(ch.try_recv().is_none());
602 }
603
604 #[tokio::test]
605 async fn stub_channel_send_queue_count_noop() {
606 let mut ch = StubChannel;
607 ch.send_queue_count(5).await.unwrap();
608 }
609
610 #[test]
613 fn loopback_pair_returns_linked_handles() {
614 let (channel, handle) = LoopbackChannel::pair(8);
615 drop(channel);
617 drop(handle);
618 }
619
620 #[tokio::test]
621 async fn loopback_cancel_signal_can_be_notified_and_awaited() {
622 let (_channel, handle) = LoopbackChannel::pair(8);
623 let signal = std::sync::Arc::clone(&handle.cancel_signal);
624 let notified = signal.notified();
626 handle.cancel_signal.notify_one();
627 notified.await; }
629
630 #[tokio::test]
631 async fn loopback_cancel_signal_shared_across_clones() {
632 let (_channel, handle) = LoopbackChannel::pair(8);
633 let signal_a = std::sync::Arc::clone(&handle.cancel_signal);
634 let signal_b = std::sync::Arc::clone(&handle.cancel_signal);
635 let notified = signal_b.notified();
636 signal_a.notify_one();
637 notified.await;
638 }
639
640 #[tokio::test]
641 async fn loopback_send_recv_round_trip() {
642 let (mut channel, handle) = LoopbackChannel::pair(8);
643 handle
644 .input_tx
645 .send(ChannelMessage {
646 text: "hello".to_owned(),
647 attachments: vec![],
648 })
649 .await
650 .unwrap();
651 let msg = channel.recv().await.unwrap().unwrap();
652 assert_eq!(msg.text, "hello");
653 }
654
655 #[tokio::test]
656 async fn loopback_recv_returns_none_when_handle_dropped() {
657 let (mut channel, handle) = LoopbackChannel::pair(8);
658 drop(handle);
659 let result = channel.recv().await.unwrap();
660 assert!(result.is_none());
661 }
662
663 #[tokio::test]
664 async fn loopback_send_produces_full_message_event() {
665 let (mut channel, mut handle) = LoopbackChannel::pair(8);
666 channel.send("world").await.unwrap();
667 let event = handle.output_rx.recv().await.unwrap();
668 assert!(matches!(event, LoopbackEvent::FullMessage(t) if t == "world"));
669 }
670
671 #[tokio::test]
672 async fn loopback_send_chunk_then_flush() {
673 let (mut channel, mut handle) = LoopbackChannel::pair(8);
674 channel.send_chunk("part1").await.unwrap();
675 channel.flush_chunks().await.unwrap();
676 let ev1 = handle.output_rx.recv().await.unwrap();
677 let ev2 = handle.output_rx.recv().await.unwrap();
678 assert!(matches!(ev1, LoopbackEvent::Chunk(t) if t == "part1"));
679 assert!(matches!(ev2, LoopbackEvent::Flush));
680 }
681
682 #[tokio::test]
683 async fn loopback_send_tool_output() {
684 let (mut channel, mut handle) = LoopbackChannel::pair(8);
685 channel
686 .send_tool_output(ToolOutputEvent {
687 tool_name: "bash",
688 body: "exit 0",
689 diff: None,
690 filter_stats: None,
691 kept_lines: None,
692 locations: None,
693 tool_call_id: "",
694 is_error: false,
695 parent_tool_use_id: None,
696 raw_response: None,
697 started_at: None,
698 })
699 .await
700 .unwrap();
701 let event = handle.output_rx.recv().await.unwrap();
702 match event {
703 LoopbackEvent::ToolOutput(data) => {
704 assert_eq!(data.tool_name, "bash");
705 assert_eq!(data.display, "exit 0");
706 assert!(data.diff.is_none());
707 assert!(data.filter_stats.is_none());
708 assert!(data.kept_lines.is_none());
709 assert!(data.locations.is_none());
710 assert_eq!(data.tool_call_id, "");
711 assert!(!data.is_error);
712 assert!(data.terminal_id.is_none());
713 assert!(data.parent_tool_use_id.is_none());
714 assert!(data.raw_response.is_none());
715 }
716 _ => panic!("expected ToolOutput event"),
717 }
718 }
719
720 #[tokio::test]
721 async fn loopback_confirm_auto_approves() {
722 let (mut channel, _handle) = LoopbackChannel::pair(8);
723 let result = channel.confirm("are you sure?").await.unwrap();
724 assert!(result);
725 }
726
727 #[tokio::test]
728 async fn loopback_send_error_when_output_closed() {
729 let (mut channel, handle) = LoopbackChannel::pair(8);
730 drop(handle);
732 let result = channel.send("too late").await;
733 assert!(matches!(result, Err(ChannelError::ChannelClosed)));
734 }
735
736 #[tokio::test]
737 async fn loopback_send_chunk_error_when_output_closed() {
738 let (mut channel, handle) = LoopbackChannel::pair(8);
739 drop(handle);
740 let result = channel.send_chunk("chunk").await;
741 assert!(matches!(result, Err(ChannelError::ChannelClosed)));
742 }
743
744 #[tokio::test]
745 async fn loopback_flush_error_when_output_closed() {
746 let (mut channel, handle) = LoopbackChannel::pair(8);
747 drop(handle);
748 let result = channel.flush_chunks().await;
749 assert!(matches!(result, Err(ChannelError::ChannelClosed)));
750 }
751
752 #[tokio::test]
753 async fn loopback_send_status_event() {
754 let (mut channel, mut handle) = LoopbackChannel::pair(8);
755 channel.send_status("working...").await.unwrap();
756 let event = handle.output_rx.recv().await.unwrap();
757 assert!(matches!(event, LoopbackEvent::Status(s) if s == "working..."));
758 }
759
760 #[tokio::test]
761 async fn loopback_send_usage_produces_usage_event() {
762 let (mut channel, mut handle) = LoopbackChannel::pair(8);
763 channel.send_usage(100, 50, 200_000).await.unwrap();
764 let event = handle.output_rx.recv().await.unwrap();
765 match event {
766 LoopbackEvent::Usage {
767 input_tokens,
768 output_tokens,
769 context_window,
770 } => {
771 assert_eq!(input_tokens, 100);
772 assert_eq!(output_tokens, 50);
773 assert_eq!(context_window, 200_000);
774 }
775 _ => panic!("expected Usage event"),
776 }
777 }
778
779 #[tokio::test]
780 async fn loopback_send_usage_error_when_closed() {
781 let (mut channel, handle) = LoopbackChannel::pair(8);
782 drop(handle);
783 let result = channel.send_usage(1, 2, 3).await;
784 assert!(matches!(result, Err(ChannelError::ChannelClosed)));
785 }
786
787 #[test]
788 fn plan_item_status_variants_are_distinct() {
789 assert!(!matches!(
790 PlanItemStatus::Pending,
791 PlanItemStatus::InProgress
792 ));
793 assert!(!matches!(
794 PlanItemStatus::InProgress,
795 PlanItemStatus::Completed
796 ));
797 assert!(!matches!(
798 PlanItemStatus::Completed,
799 PlanItemStatus::Pending
800 ));
801 }
802
803 #[test]
804 fn loopback_event_session_title_carries_string() {
805 let event = LoopbackEvent::SessionTitle("hello".to_owned());
806 assert!(matches!(event, LoopbackEvent::SessionTitle(s) if s == "hello"));
807 }
808
809 #[test]
810 fn loopback_event_plan_carries_entries() {
811 let entries = vec![
812 ("step 1".to_owned(), PlanItemStatus::Pending),
813 ("step 2".to_owned(), PlanItemStatus::InProgress),
814 ];
815 let event = LoopbackEvent::Plan(entries);
816 match event {
817 LoopbackEvent::Plan(e) => {
818 assert_eq!(e.len(), 2);
819 assert!(matches!(e[0].1, PlanItemStatus::Pending));
820 assert!(matches!(e[1].1, PlanItemStatus::InProgress));
821 }
822 _ => panic!("expected Plan event"),
823 }
824 }
825
826 #[tokio::test]
827 async fn loopback_send_tool_start_produces_tool_start_event() {
828 let (mut channel, mut handle) = LoopbackChannel::pair(8);
829 channel
830 .send_tool_start(ToolStartEvent {
831 tool_name: "shell",
832 tool_call_id: "tc-001",
833 params: Some(serde_json::json!({"command": "ls"})),
834 parent_tool_use_id: None,
835 })
836 .await
837 .unwrap();
838 let event = handle.output_rx.recv().await.unwrap();
839 match event {
840 LoopbackEvent::ToolStart(data) => {
841 assert_eq!(data.tool_name, "shell");
842 assert_eq!(data.tool_call_id, "tc-001");
843 assert!(data.params.is_some());
844 assert!(data.parent_tool_use_id.is_none());
845 }
846 _ => panic!("expected ToolStart event"),
847 }
848 }
849
850 #[tokio::test]
851 async fn loopback_send_tool_start_with_parent_id() {
852 let (mut channel, mut handle) = LoopbackChannel::pair(8);
853 channel
854 .send_tool_start(ToolStartEvent {
855 tool_name: "web",
856 tool_call_id: "tc-002",
857 params: None,
858 parent_tool_use_id: Some("parent-123".into()),
859 })
860 .await
861 .unwrap();
862 let event = handle.output_rx.recv().await.unwrap();
863 assert!(matches!(
864 event,
865 LoopbackEvent::ToolStart(ref data) if data.parent_tool_use_id.as_deref() == Some("parent-123")
866 ));
867 }
868
869 #[tokio::test]
870 async fn loopback_send_tool_start_error_when_output_closed() {
871 let (mut channel, handle) = LoopbackChannel::pair(8);
872 drop(handle);
873 let result = channel
874 .send_tool_start(ToolStartEvent {
875 tool_name: "shell",
876 tool_call_id: "tc-003",
877 params: None,
878 parent_tool_use_id: None,
879 })
880 .await;
881 assert!(matches!(result, Err(ChannelError::ChannelClosed)));
882 }
883
884 #[tokio::test]
885 async fn default_send_tool_output_formats_message() {
886 let mut ch = StubChannel;
887 ch.send_tool_output(ToolOutputEvent {
889 tool_name: "bash",
890 body: "hello",
891 diff: None,
892 filter_stats: None,
893 kept_lines: None,
894 locations: None,
895 tool_call_id: "id",
896 is_error: false,
897 parent_tool_use_id: None,
898 raw_response: None,
899 started_at: None,
900 })
901 .await
902 .unwrap();
903 }
904}