1use serde::{Deserialize, Serialize};
7use serde_json::{Map, Value};
8
9use super::parts::{
10 BuiltinToolCallPart, FilePart, TextPart, ThinkingPart, ToolCallArgs, ToolCallPart,
11};
12use super::response::ModelResponsePart;
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16#[serde(tag = "event_kind", rename_all = "snake_case")]
17pub enum ModelResponseStreamEvent {
18 PartStart(PartStartEvent),
20 PartDelta(PartDeltaEvent),
22 PartEnd(PartEndEvent),
24}
25
26impl ModelResponseStreamEvent {
27 #[must_use]
29 pub fn part_start(index: usize, part: ModelResponsePart) -> Self {
30 Self::PartStart(PartStartEvent { index, part })
31 }
32
33 #[must_use]
35 pub fn text_delta(index: usize, content_delta: impl Into<String>) -> Self {
36 Self::PartDelta(PartDeltaEvent {
37 index,
38 delta: ModelResponsePartDelta::Text(TextPartDelta::new(content_delta)),
39 })
40 }
41
42 #[must_use]
44 pub fn tool_call_delta(index: usize, args_delta: impl Into<String>) -> Self {
45 Self::PartDelta(PartDeltaEvent {
46 index,
47 delta: ModelResponsePartDelta::ToolCall(ToolCallPartDelta::new(args_delta)),
48 })
49 }
50
51 #[must_use]
53 pub fn thinking_delta(index: usize, content_delta: impl Into<String>) -> Self {
54 Self::PartDelta(PartDeltaEvent {
55 index,
56 delta: ModelResponsePartDelta::Thinking(ThinkingPartDelta::new(content_delta)),
57 })
58 }
59
60 #[must_use]
62 pub fn builtin_tool_call_delta(index: usize, args_delta: impl Into<String>) -> Self {
63 Self::PartDelta(PartDeltaEvent {
64 index,
65 delta: ModelResponsePartDelta::BuiltinToolCall(BuiltinToolCallPartDelta::new(
66 args_delta,
67 )),
68 })
69 }
70
71 #[must_use]
76 pub fn file_part(index: usize, part: FilePart) -> Self {
77 Self::PartStart(PartStartEvent {
78 index,
79 part: ModelResponsePart::File(part),
80 })
81 }
82
83 #[must_use]
85 pub fn builtin_tool_call_start(index: usize, part: BuiltinToolCallPart) -> Self {
86 Self::PartStart(PartStartEvent {
87 index,
88 part: ModelResponsePart::BuiltinToolCall(part),
89 })
90 }
91
92 #[must_use]
94 pub fn part_end(index: usize) -> Self {
95 Self::PartEnd(PartEndEvent { index })
96 }
97
98 #[must_use]
100 pub fn index(&self) -> usize {
101 match self {
102 Self::PartStart(e) => e.index,
103 Self::PartDelta(e) => e.index,
104 Self::PartEnd(e) => e.index,
105 }
106 }
107
108 #[must_use]
110 pub fn is_start(&self) -> bool {
111 matches!(self, Self::PartStart(_))
112 }
113
114 #[must_use]
116 pub fn is_delta(&self) -> bool {
117 matches!(self, Self::PartDelta(_))
118 }
119
120 #[must_use]
122 pub fn is_end(&self) -> bool {
123 matches!(self, Self::PartEnd(_))
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
129pub struct PartStartEvent {
130 pub index: usize,
132 pub part: ModelResponsePart,
134}
135
136impl PartStartEvent {
137 #[must_use]
139 pub fn new(index: usize, part: ModelResponsePart) -> Self {
140 Self { index, part }
141 }
142
143 #[must_use]
145 pub fn text(index: usize, content: impl Into<String>) -> Self {
146 Self::new(index, ModelResponsePart::Text(TextPart::new(content)))
147 }
148
149 #[must_use]
151 pub fn tool_call(index: usize, tool_name: impl Into<String>) -> Self {
152 Self::new(
153 index,
154 ModelResponsePart::ToolCall(ToolCallPart::new(tool_name, serde_json::Value::Null)),
155 )
156 }
157
158 #[must_use]
160 pub fn thinking(index: usize, content: impl Into<String>) -> Self {
161 Self::new(
162 index,
163 ModelResponsePart::Thinking(ThinkingPart::new(content)),
164 )
165 }
166
167 #[must_use]
169 pub fn file(index: usize, part: FilePart) -> Self {
170 Self::new(index, ModelResponsePart::File(part))
171 }
172
173 #[must_use]
175 pub fn builtin_tool_call(
176 index: usize,
177 tool_name: impl Into<String>,
178 args: impl Into<ToolCallArgs>,
179 ) -> Self {
180 Self::new(
181 index,
182 ModelResponsePart::BuiltinToolCall(BuiltinToolCallPart::new(tool_name, args)),
183 )
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
189pub struct PartDeltaEvent {
190 pub index: usize,
192 pub delta: ModelResponsePartDelta,
194}
195
196impl PartDeltaEvent {
197 #[must_use]
199 pub fn new(index: usize, delta: ModelResponsePartDelta) -> Self {
200 Self { index, delta }
201 }
202
203 #[must_use]
205 pub fn text(index: usize, content: impl Into<String>) -> Self {
206 Self::new(
207 index,
208 ModelResponsePartDelta::Text(TextPartDelta::new(content)),
209 )
210 }
211
212 #[must_use]
214 pub fn tool_call_args(index: usize, args: impl Into<String>) -> Self {
215 Self::new(
216 index,
217 ModelResponsePartDelta::ToolCall(ToolCallPartDelta::new(args)),
218 )
219 }
220
221 #[must_use]
223 pub fn thinking(index: usize, content: impl Into<String>) -> Self {
224 Self::new(
225 index,
226 ModelResponsePartDelta::Thinking(ThinkingPartDelta::new(content)),
227 )
228 }
229
230 #[must_use]
232 pub fn builtin_tool_call_args(index: usize, args: impl Into<String>) -> Self {
233 Self::new(
234 index,
235 ModelResponsePartDelta::BuiltinToolCall(BuiltinToolCallPartDelta::new(args)),
236 )
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242#[serde(tag = "delta_kind", rename_all = "snake_case")]
243pub enum ModelResponsePartDelta {
244 Text(TextPartDelta),
246 ToolCall(ToolCallPartDelta),
248 Thinking(ThinkingPartDelta),
250 BuiltinToolCall(BuiltinToolCallPartDelta),
252}
253
254impl ModelResponsePartDelta {
255 #[must_use]
257 pub fn is_text(&self) -> bool {
258 matches!(self, Self::Text(_))
259 }
260
261 #[must_use]
263 pub fn is_tool_call(&self) -> bool {
264 matches!(self, Self::ToolCall(_))
265 }
266
267 #[must_use]
269 pub fn is_thinking(&self) -> bool {
270 matches!(self, Self::Thinking(_))
271 }
272
273 #[must_use]
275 pub fn is_builtin_tool_call(&self) -> bool {
276 matches!(self, Self::BuiltinToolCall(_))
277 }
278
279 #[must_use]
281 pub fn content_delta(&self) -> Option<&str> {
282 match self {
283 Self::Text(d) => Some(&d.content_delta),
284 Self::Thinking(d) => Some(&d.content_delta),
285 Self::ToolCall(_) => None,
286 Self::BuiltinToolCall(_) => None,
287 }
288 }
289
290 #[must_use]
295 pub fn apply(&self, part: &mut ModelResponsePart) -> bool {
296 match (self, part) {
297 (Self::Text(delta), ModelResponsePart::Text(text_part)) => {
298 delta.apply(text_part);
299 true
300 }
301 (Self::ToolCall(delta), ModelResponsePart::ToolCall(tool_part)) => {
302 delta.apply(tool_part);
303 true
304 }
305 (Self::Thinking(delta), ModelResponsePart::Thinking(thinking_part)) => {
306 delta.apply(thinking_part);
307 true
308 }
309 (Self::BuiltinToolCall(delta), ModelResponsePart::BuiltinToolCall(builtin_part)) => {
310 delta.apply(builtin_part);
311 true
312 }
313 _ => false,
314 }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
320pub struct TextPartDelta {
321 pub content_delta: String,
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub provider_details: Option<Map<String, Value>>,
326}
327
328impl TextPartDelta {
329 #[must_use]
331 pub fn new(content: impl Into<String>) -> Self {
332 Self {
333 content_delta: content.into(),
334 provider_details: None,
335 }
336 }
337
338 #[must_use]
340 pub fn with_provider_details(mut self, details: Map<String, Value>) -> Self {
341 self.provider_details = Some(details);
342 self
343 }
344
345 #[must_use]
347 pub fn is_empty(&self) -> bool {
348 self.content_delta.is_empty() && self.provider_details.is_none()
349 }
350
351 pub fn apply(&self, part: &mut TextPart) {
353 if !self.content_delta.is_empty() {
354 part.content.push_str(&self.content_delta);
355 }
356 if let Some(ref details) = self.provider_details {
357 match &mut part.provider_details {
358 Some(existing) => {
359 for (key, value) in details {
361 existing.insert(key.clone(), value.clone());
362 }
363 }
364 None => part.provider_details = Some(details.clone()),
365 }
366 }
367 }
368}
369
370impl Default for TextPartDelta {
371 fn default() -> Self {
372 Self::new("")
373 }
374}
375
376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
378pub struct ToolCallPartDelta {
379 pub args_delta: String,
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub tool_call_id: Option<String>,
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub provider_details: Option<Map<String, Value>>,
387}
388
389impl ToolCallPartDelta {
390 #[must_use]
392 pub fn new(args: impl Into<String>) -> Self {
393 Self {
394 args_delta: args.into(),
395 tool_call_id: None,
396 provider_details: None,
397 }
398 }
399
400 #[must_use]
402 pub fn with_tool_call_id(mut self, id: impl Into<String>) -> Self {
403 self.tool_call_id = Some(id.into());
404 self
405 }
406
407 #[must_use]
409 pub fn with_provider_details(mut self, details: Map<String, Value>) -> Self {
410 self.provider_details = Some(details);
411 self
412 }
413
414 #[must_use]
416 pub fn is_empty(&self) -> bool {
417 self.args_delta.is_empty() && self.tool_call_id.is_none() && self.provider_details.is_none()
418 }
419
420 pub fn apply(&self, part: &mut ToolCallPart) {
422 if !self.args_delta.is_empty() {
423 let current = part
425 .args
426 .to_json_string()
427 .unwrap_or_else(|_| part.args.to_json().to_string());
428
429 let new_args = if current == "{}" || current.is_empty() {
430 self.args_delta.clone()
432 } else {
433 format!("{}{}", current, self.args_delta)
435 };
436 part.args = ToolCallArgs::String(new_args);
437 }
438 if self.tool_call_id.is_some() && part.tool_call_id.is_none() {
439 part.tool_call_id = self.tool_call_id.clone();
440 }
441 if let Some(ref details) = self.provider_details {
442 match &mut part.provider_details {
443 Some(existing) => {
444 for (key, value) in details {
446 existing.insert(key.clone(), value.clone());
447 }
448 }
449 None => part.provider_details = Some(details.clone()),
450 }
451 }
452 }
453}
454
455impl Default for ToolCallPartDelta {
456 fn default() -> Self {
457 Self::new("")
458 }
459}
460
461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
463pub struct ThinkingPartDelta {
464 pub content_delta: String,
466 #[serde(skip_serializing_if = "Option::is_none")]
468 pub signature_delta: Option<String>,
469 #[serde(skip_serializing_if = "Option::is_none")]
471 pub provider_name: Option<String>,
472 #[serde(skip_serializing_if = "Option::is_none")]
474 pub provider_details: Option<Map<String, Value>>,
475}
476
477impl ThinkingPartDelta {
478 #[must_use]
480 pub fn new(content: impl Into<String>) -> Self {
481 Self {
482 content_delta: content.into(),
483 signature_delta: None,
484 provider_name: None,
485 provider_details: None,
486 }
487 }
488
489 #[must_use]
491 pub fn with_signature_delta(mut self, sig: impl Into<String>) -> Self {
492 self.signature_delta = Some(sig.into());
493 self
494 }
495
496 #[must_use]
498 pub fn with_provider_name(mut self, name: impl Into<String>) -> Self {
499 self.provider_name = Some(name.into());
500 self
501 }
502
503 #[must_use]
505 pub fn with_provider_details(mut self, details: Map<String, Value>) -> Self {
506 self.provider_details = Some(details);
507 self
508 }
509
510 #[must_use]
512 pub fn is_empty(&self) -> bool {
513 self.content_delta.is_empty()
514 && self.signature_delta.is_none()
515 && self.provider_name.is_none()
516 && self.provider_details.is_none()
517 }
518
519 pub fn apply(&self, part: &mut ThinkingPart) {
521 if !self.content_delta.is_empty() {
522 part.content.push_str(&self.content_delta);
523 }
524 if let Some(ref sig_delta) = self.signature_delta {
525 match &mut part.signature {
526 Some(existing) => existing.push_str(sig_delta),
527 None => part.signature = Some(sig_delta.clone()),
528 }
529 }
530 if self.provider_name.is_some() {
531 part.provider_name = self.provider_name.clone();
532 }
533 if let Some(ref details) = self.provider_details {
534 match &mut part.provider_details {
535 Some(existing) => {
536 for (key, value) in details {
538 existing.insert(key.clone(), value.clone());
539 }
540 }
541 None => part.provider_details = Some(details.clone()),
542 }
543 }
544 }
545}
546
547impl Default for ThinkingPartDelta {
548 fn default() -> Self {
549 Self::new("")
550 }
551}
552
553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
558pub struct BuiltinToolCallPartDelta {
559 pub args_delta: String,
561 #[serde(skip_serializing_if = "Option::is_none")]
563 pub tool_call_id: Option<String>,
564 #[serde(skip_serializing_if = "Option::is_none")]
566 pub provider_details: Option<Map<String, Value>>,
567}
568
569impl BuiltinToolCallPartDelta {
570 #[must_use]
572 pub fn new(args: impl Into<String>) -> Self {
573 Self {
574 args_delta: args.into(),
575 tool_call_id: None,
576 provider_details: None,
577 }
578 }
579
580 #[must_use]
582 pub fn with_tool_call_id(mut self, id: impl Into<String>) -> Self {
583 self.tool_call_id = Some(id.into());
584 self
585 }
586
587 #[must_use]
589 pub fn with_provider_details(mut self, details: Map<String, Value>) -> Self {
590 self.provider_details = Some(details);
591 self
592 }
593
594 #[must_use]
596 pub fn is_empty(&self) -> bool {
597 self.args_delta.is_empty() && self.tool_call_id.is_none() && self.provider_details.is_none()
598 }
599
600 pub fn apply(&self, part: &mut BuiltinToolCallPart) {
602 if !self.args_delta.is_empty() {
603 let current = part
605 .args
606 .to_json_string()
607 .unwrap_or_else(|_| part.args.to_json().to_string());
608
609 let new_args = if current == "{}" || current.is_empty() {
610 self.args_delta.clone()
612 } else {
613 format!("{}{}", current, self.args_delta)
615 };
616 part.args = ToolCallArgs::String(new_args);
617 }
618 if self.tool_call_id.is_some() && part.tool_call_id.is_none() {
619 part.tool_call_id = self.tool_call_id.clone();
620 }
621 if let Some(ref details) = self.provider_details {
622 match &mut part.provider_details {
623 Some(existing) => {
624 for (key, value) in details {
626 existing.insert(key.clone(), value.clone());
627 }
628 }
629 None => part.provider_details = Some(details.clone()),
630 }
631 }
632 }
633}
634
635impl Default for BuiltinToolCallPartDelta {
636 fn default() -> Self {
637 Self::new("")
638 }
639}
640
641#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
643pub struct PartEndEvent {
644 pub index: usize,
646}
647
648impl PartEndEvent {
649 #[must_use]
651 pub fn new(index: usize) -> Self {
652 Self { index }
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659
660 #[test]
661 fn test_part_start_event() {
662 let event = PartStartEvent::text(0, "Hello");
663 assert_eq!(event.index, 0);
664 assert!(matches!(event.part, ModelResponsePart::Text(_)));
665 }
666
667 #[test]
668 fn test_part_delta_event() {
669 let event = PartDeltaEvent::text(0, " world");
670 assert_eq!(event.index, 0);
671 assert!(event.delta.is_text());
672 assert_eq!(event.delta.content_delta(), Some(" world"));
673 }
674
675 #[test]
676 fn test_stream_event_helpers() {
677 let start = ModelResponseStreamEvent::part_start(0, ModelResponsePart::text("Hello"));
678 assert!(start.is_start());
679 assert_eq!(start.index(), 0);
680
681 let delta = ModelResponseStreamEvent::text_delta(0, " world");
682 assert!(delta.is_delta());
683
684 let end = ModelResponseStreamEvent::part_end(0);
685 assert!(end.is_end());
686 }
687
688 #[test]
689 fn test_serde_roundtrip() {
690 let event = ModelResponseStreamEvent::text_delta(0, "Hello");
691 let json = serde_json::to_string(&event).unwrap();
692 let parsed: ModelResponseStreamEvent = serde_json::from_str(&json).unwrap();
693 assert_eq!(event, parsed);
694 }
695
696 #[test]
697 fn test_text_delta_apply() {
698 let mut part = TextPart::new("Hello");
699 let delta = TextPartDelta::new(" world");
700 delta.apply(&mut part);
701 assert_eq!(part.content, "Hello world");
702 }
703
704 #[test]
705 fn test_text_delta_apply_with_provider_details() {
706 let mut part = TextPart::new("Hello");
707
708 let mut details = Map::new();
709 details.insert("model".to_string(), Value::String("gpt-4".to_string()));
710
711 let delta = TextPartDelta::new(" world").with_provider_details(details);
712 delta.apply(&mut part);
713
714 assert_eq!(part.content, "Hello world");
715 assert!(part.provider_details.is_some());
716 assert_eq!(
717 part.provider_details.as_ref().unwrap().get("model"),
718 Some(&Value::String("gpt-4".to_string()))
719 );
720 }
721
722 #[test]
723 fn test_text_delta_merge_provider_details() {
724 let mut initial_details = Map::new();
725 initial_details.insert("key1".to_string(), Value::String("value1".to_string()));
726
727 let mut part = TextPart::new("Hello").with_provider_details(initial_details);
728
729 let mut new_details = Map::new();
730 new_details.insert("key2".to_string(), Value::String("value2".to_string()));
731
732 let delta = TextPartDelta::new("").with_provider_details(new_details);
733 delta.apply(&mut part);
734
735 let details = part.provider_details.as_ref().unwrap();
736 assert_eq!(details.len(), 2);
737 assert_eq!(
738 details.get("key1"),
739 Some(&Value::String("value1".to_string()))
740 );
741 assert_eq!(
742 details.get("key2"),
743 Some(&Value::String("value2".to_string()))
744 );
745 }
746
747 #[test]
748 fn test_thinking_delta_apply() {
749 let mut part = ThinkingPart::new("Initial thought");
750 let delta = ThinkingPartDelta::new(" continued...");
751 delta.apply(&mut part);
752 assert_eq!(part.content, "Initial thought continued...");
753 }
754
755 #[test]
756 fn test_thinking_delta_apply_with_signature() {
757 let mut part = ThinkingPart::new("Thinking");
758
759 let delta = ThinkingPartDelta::new("")
760 .with_signature_delta("sig123")
761 .with_provider_name("anthropic");
762 delta.apply(&mut part);
763
764 assert_eq!(part.signature, Some("sig123".to_string()));
765 assert_eq!(part.provider_name, Some("anthropic".to_string()));
766 }
767
768 #[test]
769 fn test_thinking_delta_signature_accumulation() {
770 let mut part = ThinkingPart::new("Thinking").with_signature("sig1");
771
772 let delta = ThinkingPartDelta::new("").with_signature_delta("23");
773 delta.apply(&mut part);
774
775 assert_eq!(part.signature, Some("sig123".to_string()));
776 }
777
778 #[test]
779 fn test_tool_call_delta_apply() {
780 let mut part = ToolCallPart::new("get_weather", serde_json::json!({}));
781
782 let delta = ToolCallPartDelta::new(r#"{"city":"NYC"}"#).with_tool_call_id("call_123");
783 delta.apply(&mut part);
784
785 assert_eq!(part.tool_call_id, Some("call_123".to_string()));
786 assert!(part.args.to_json_string().unwrap().contains("city"));
788 }
789
790 #[test]
791 fn test_tool_call_delta_doesnt_overwrite_id() {
792 let mut part =
793 ToolCallPart::new("search", serde_json::json!({})).with_tool_call_id("original_id");
794
795 let delta = ToolCallPartDelta::new("").with_tool_call_id("new_id");
796 delta.apply(&mut part);
797
798 assert_eq!(part.tool_call_id, Some("original_id".to_string()));
800 }
801
802 #[test]
803 fn test_model_response_part_delta_apply() {
804 let mut text_part = ModelResponsePart::Text(TextPart::new("Hello"));
805 let delta = ModelResponsePartDelta::Text(TextPartDelta::new(" world"));
806
807 assert!(delta.apply(&mut text_part));
808
809 if let ModelResponsePart::Text(ref text) = text_part {
810 assert_eq!(text.content, "Hello world");
811 } else {
812 panic!("Expected Text part");
813 }
814 }
815
816 #[test]
817 fn test_model_response_part_delta_apply_type_mismatch() {
818 let mut text_part = ModelResponsePart::Text(TextPart::new("Hello"));
819 let delta = ModelResponsePartDelta::Thinking(ThinkingPartDelta::new("thinking"));
820
821 assert!(!delta.apply(&mut text_part));
823
824 if let ModelResponsePart::Text(ref text) = text_part {
826 assert_eq!(text.content, "Hello");
827 } else {
828 panic!("Expected Text part");
829 }
830 }
831
832 #[test]
833 fn test_delta_is_empty() {
834 let text_delta = TextPartDelta::default();
836 assert!(text_delta.is_empty());
837
838 let text_delta = TextPartDelta::new("content");
840 assert!(!text_delta.is_empty());
841
842 let mut details = Map::new();
844 details.insert("key".to_string(), Value::Null);
845 let text_delta = TextPartDelta::new("").with_provider_details(details);
846 assert!(!text_delta.is_empty());
847
848 let thinking_delta = ThinkingPartDelta::default();
850 assert!(thinking_delta.is_empty());
851
852 let thinking_delta = ThinkingPartDelta::new("").with_signature_delta("sig");
854 assert!(!thinking_delta.is_empty());
855
856 let tool_delta = ToolCallPartDelta::default();
858 assert!(tool_delta.is_empty());
859
860 let tool_delta = ToolCallPartDelta::new("").with_tool_call_id("id");
862 assert!(!tool_delta.is_empty());
863 }
864
865 #[test]
866 fn test_delta_builders() {
867 let text_delta = TextPartDelta::new("content").with_provider_details(Map::new());
868 assert!(!text_delta.is_empty());
869
870 let thinking_delta = ThinkingPartDelta::new("thought")
871 .with_signature_delta("sig")
872 .with_provider_name("provider")
873 .with_provider_details(Map::new());
874 assert!(!thinking_delta.is_empty());
875
876 let tool_delta = ToolCallPartDelta::new(r#"{}"#)
877 .with_tool_call_id("call_1")
878 .with_provider_details(Map::new());
879 assert!(!tool_delta.is_empty());
880 }
881
882 #[test]
883 fn test_serde_roundtrip_with_new_fields() {
884 let mut details = Map::new();
886 details.insert("key".to_string(), Value::String("value".to_string()));
887 let delta = TextPartDelta::new("hello").with_provider_details(details);
888 let json = serde_json::to_string(&delta).unwrap();
889 let parsed: TextPartDelta = serde_json::from_str(&json).unwrap();
890 assert_eq!(delta, parsed);
891
892 let thinking_delta = ThinkingPartDelta::new("thought")
894 .with_signature_delta("sig")
895 .with_provider_name("anthropic");
896 let json = serde_json::to_string(&thinking_delta).unwrap();
897 let parsed: ThinkingPartDelta = serde_json::from_str(&json).unwrap();
898 assert_eq!(thinking_delta, parsed);
899
900 let tool_delta = ToolCallPartDelta::new(r#"{"a":1}"#).with_tool_call_id("call_123");
902 let json = serde_json::to_string(&tool_delta).unwrap();
903 let parsed: ToolCallPartDelta = serde_json::from_str(&json).unwrap();
904 assert_eq!(tool_delta, parsed);
905 }
906
907 #[test]
908 fn test_serde_skip_none_fields() {
909 let delta = TextPartDelta::new("hello");
911 let json = serde_json::to_string(&delta).unwrap();
912
913 assert!(json.contains("content_delta"));
914 assert!(!json.contains("provider_details"));
915
916 let thinking_delta = ThinkingPartDelta::new("thought");
917 let json = serde_json::to_string(&thinking_delta).unwrap();
918
919 assert!(!json.contains("signature_delta"));
920 assert!(!json.contains("provider_name"));
921 assert!(!json.contains("provider_details"));
922 }
923
924 #[test]
925 fn test_backward_compat_deserialization() {
926 let old_json = r#"{"content_delta":"hello"}"#;
928 let delta: TextPartDelta = serde_json::from_str(old_json).unwrap();
929 assert_eq!(delta.content_delta, "hello");
930 assert!(delta.provider_details.is_none());
931
932 let old_json = r#"{"content_delta":"thinking"}"#;
933 let delta: ThinkingPartDelta = serde_json::from_str(old_json).unwrap();
934 assert_eq!(delta.content_delta, "thinking");
935 assert!(delta.signature_delta.is_none());
936 assert!(delta.provider_name.is_none());
937
938 let old_json = r#"{"args_delta":"{}"}"#;
939 let delta: ToolCallPartDelta = serde_json::from_str(old_json).unwrap();
940 assert_eq!(delta.args_delta, "{}");
941 assert!(delta.tool_call_id.is_none());
942 }
943
944 #[test]
945 fn test_builtin_tool_call_delta() {
946 let delta =
947 BuiltinToolCallPartDelta::new(r#"{"query":"rust"}"#).with_tool_call_id("builtin_123");
948
949 assert!(!delta.is_empty());
950 assert_eq!(delta.args_delta, r#"{"query":"rust"}"#);
951 assert_eq!(delta.tool_call_id, Some("builtin_123".to_string()));
952 }
953
954 #[test]
955 fn test_builtin_tool_call_delta_apply() {
956 let mut part = BuiltinToolCallPart::new("web_search", serde_json::json!({}));
957
958 let delta = BuiltinToolCallPartDelta::new(r#"{"q":"test"}"#).with_tool_call_id("call_456");
959 delta.apply(&mut part);
960
961 assert_eq!(part.tool_call_id, Some("call_456".to_string()));
962 assert!(part.args.to_json_string().unwrap().contains("test"));
963 }
964
965 #[test]
966 fn test_file_part_event() {
967 let file = FilePart::from_bytes(vec![0x89, 0x50, 0x4E, 0x47], "image/png");
968 let event = ModelResponseStreamEvent::file_part(0, file.clone());
969
970 assert!(event.is_start());
971 assert_eq!(event.index(), 0);
972
973 if let ModelResponseStreamEvent::PartStart(start) = event {
974 assert!(matches!(start.part, ModelResponsePart::File(_)));
975 } else {
976 panic!("Expected PartStart");
977 }
978 }
979
980 #[test]
981 fn test_builtin_tool_call_start_event() {
982 let part = BuiltinToolCallPart::new("web_search", serde_json::json!({"query": "rust"}))
983 .with_tool_call_id("call_123");
984 let event = ModelResponseStreamEvent::builtin_tool_call_start(0, part);
985
986 assert!(event.is_start());
987
988 if let ModelResponseStreamEvent::PartStart(start) = event {
989 assert!(matches!(start.part, ModelResponsePart::BuiltinToolCall(_)));
990 } else {
991 panic!("Expected PartStart");
992 }
993 }
994
995 #[test]
996 fn test_builtin_tool_call_delta_event() {
997 let event = ModelResponseStreamEvent::builtin_tool_call_delta(0, r#"{"q":"rust"}"#);
998
999 assert!(event.is_delta());
1000
1001 if let ModelResponseStreamEvent::PartDelta(delta_event) = event {
1002 assert!(delta_event.delta.is_builtin_tool_call());
1003 } else {
1004 panic!("Expected PartDelta");
1005 }
1006 }
1007
1008 #[test]
1009 fn test_part_start_event_builtin_tool_call() {
1010 let event = PartStartEvent::builtin_tool_call(
1011 0,
1012 "code_execution",
1013 serde_json::json!({"code": "print(1)"}),
1014 );
1015
1016 assert_eq!(event.index, 0);
1017 assert!(matches!(event.part, ModelResponsePart::BuiltinToolCall(_)));
1018 }
1019
1020 #[test]
1021 fn test_part_delta_event_builtin_tool_call() {
1022 let event = PartDeltaEvent::builtin_tool_call_args(0, r#"{"more":"args"}"#);
1023
1024 assert_eq!(event.index, 0);
1025 assert!(event.delta.is_builtin_tool_call());
1026 assert_eq!(event.delta.content_delta(), None); }
1028
1029 #[test]
1030 fn test_serde_roundtrip_builtin_tool_call_delta() {
1031 let delta = BuiltinToolCallPartDelta::new(r#"{"q":"test"}"#).with_tool_call_id("call_123");
1032
1033 let json = serde_json::to_string(&delta).unwrap();
1034 let parsed: BuiltinToolCallPartDelta = serde_json::from_str(&json).unwrap();
1035
1036 assert_eq!(delta, parsed);
1037 }
1038
1039 #[test]
1040 fn test_model_response_part_delta_apply_builtin() {
1041 let mut part = ModelResponsePart::BuiltinToolCall(BuiltinToolCallPart::new(
1042 "search",
1043 serde_json::json!({}),
1044 ));
1045 let delta = ModelResponsePartDelta::BuiltinToolCall(BuiltinToolCallPartDelta::new(
1046 r#"{"q":"rust"}"#,
1047 ));
1048
1049 assert!(delta.apply(&mut part));
1050
1051 if let ModelResponsePart::BuiltinToolCall(ref builtin) = part {
1052 assert!(builtin.args.to_json_string().unwrap().contains("rust"));
1053 } else {
1054 panic!("Expected BuiltinToolCall part");
1055 }
1056 }
1057}