1use serde::{Deserialize, Serialize};
20
21use crate::content::{
22 Annotation, CodeExecutionLanguage, Content, FileSearchResultItem, GoogleMapsResultItem,
23 GoogleSearchResultItem, Resolution, UrlContextResultItem,
24};
25
26#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
34#[serde(default)]
35pub struct StepError {
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub code: Option<i64>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub message: Option<String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub details: Option<Vec<serde_json::Value>>,
45}
46
47#[derive(Clone, Debug, PartialEq)]
58#[non_exhaustive]
59pub enum FunctionResultPayload {
60 Text(String),
62 Json(serde_json::Value),
66 Contents(Vec<Content>),
68}
69
70impl FunctionResultPayload {
71 #[must_use]
73 pub fn as_text(&self) -> Option<&str> {
74 match self {
75 Self::Text(t) => Some(t),
76 _ => None,
77 }
78 }
79
80 #[must_use]
82 pub fn as_json(&self) -> Option<&serde_json::Value> {
83 match self {
84 Self::Json(v) => Some(v),
85 _ => None,
86 }
87 }
88
89 #[must_use]
91 pub fn as_contents(&self) -> Option<&[Content]> {
92 match self {
93 Self::Contents(c) => Some(c),
94 _ => None,
95 }
96 }
97
98 #[must_use]
100 pub fn to_value(&self) -> serde_json::Value {
101 match self {
102 Self::Text(t) => serde_json::Value::String(t.clone()),
103 Self::Json(v) => v.clone(),
104 Self::Contents(c) => serde_json::to_value(c).unwrap_or(serde_json::Value::Null),
105 }
106 }
107}
108
109impl From<serde_json::Value> for FunctionResultPayload {
110 fn from(value: serde_json::Value) -> Self {
115 match value {
116 serde_json::Value::String(s) => Self::Text(s),
117 other => Self::Json(other),
118 }
119 }
120}
121
122impl From<&str> for FunctionResultPayload {
123 fn from(value: &str) -> Self {
124 Self::Text(value.to_string())
125 }
126}
127
128impl From<String> for FunctionResultPayload {
129 fn from(value: String) -> Self {
130 Self::Text(value)
131 }
132}
133
134impl From<Vec<Content>> for FunctionResultPayload {
135 fn from(value: Vec<Content>) -> Self {
136 Self::Contents(value)
137 }
138}
139
140impl Serialize for FunctionResultPayload {
141 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142 where
143 S: serde::Serializer,
144 {
145 match self {
146 Self::Text(t) => serializer.serialize_str(t),
147 Self::Json(v) => v.serialize(serializer),
148 Self::Contents(c) => c.serialize(serializer),
149 }
150 }
151}
152
153impl<'de> Deserialize<'de> for FunctionResultPayload {
154 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
155 where
156 D: serde::Deserializer<'de>,
157 {
158 let value = serde_json::Value::deserialize(deserializer)?;
159 Ok(Self::from_value(value))
160 }
161}
162
163impl FunctionResultPayload {
164 #[must_use]
169 pub fn from_value(value: serde_json::Value) -> Self {
170 match value {
171 serde_json::Value::String(s) => Self::Text(s),
172 serde_json::Value::Array(items) => {
173 let looks_like_contents = !items.is_empty()
174 && items
175 .iter()
176 .all(|v| v.is_object() && v.get("type").is_some_and(|t| t.is_string()));
177 if looks_like_contents {
178 match serde_json::from_value::<Vec<Content>>(serde_json::Value::Array(
179 items.clone(),
180 )) {
181 Ok(contents) => Self::Contents(contents),
182 Err(_) => Self::Json(serde_json::Value::Array(items)),
183 }
184 } else {
185 Self::Json(serde_json::Value::Array(items))
186 }
187 }
188 other => Self::Json(other),
189 }
190 }
191}
192
193#[derive(Clone, Debug, PartialEq)]
224#[non_exhaustive]
225pub enum Step {
226 UserInput {
228 content: Vec<Content>,
230 },
231 ModelOutput {
233 content: Vec<Content>,
235 error: Option<StepError>,
237 },
238 Thought {
240 signature: Option<String>,
243 summary: Vec<Content>,
246 },
247 FunctionCall {
250 id: String,
252 name: String,
254 arguments: serde_json::Value,
256 signature: Option<String>,
260 },
261 FunctionResult {
264 call_id: String,
266 name: Option<String>,
268 result: FunctionResultPayload,
270 is_error: Option<bool>,
272 signature: Option<String>,
275 },
276 CodeExecutionCall {
280 id: String,
282 language: CodeExecutionLanguage,
284 code: String,
286 signature: Option<String>,
288 },
289 CodeExecutionResult {
291 call_id: String,
293 result: String,
295 is_error: bool,
297 signature: Option<String>,
299 },
300 UrlContextCall {
302 id: String,
304 urls: Vec<String>,
306 signature: Option<String>,
308 },
309 UrlContextResult {
311 call_id: String,
313 result: Vec<UrlContextResultItem>,
315 is_error: Option<bool>,
317 signature: Option<String>,
319 },
320 GoogleSearchCall {
322 id: String,
324 queries: Vec<String>,
326 search_type: Option<crate::tools::SearchType>,
328 signature: Option<String>,
330 },
331 GoogleSearchResult {
333 call_id: String,
335 result: Vec<GoogleSearchResultItem>,
337 is_error: Option<bool>,
339 signature: Option<String>,
341 },
342 McpServerToolCall {
344 id: String,
346 name: String,
348 server_name: String,
350 arguments: serde_json::Value,
352 },
353 McpServerToolResult {
355 call_id: String,
357 name: Option<String>,
359 server_name: Option<String>,
361 result: FunctionResultPayload,
363 },
364 FileSearchCall {
366 id: String,
368 signature: Option<String>,
370 },
371 FileSearchResult {
373 call_id: String,
375 result: Vec<FileSearchResultItem>,
379 signature: Option<String>,
381 },
382 GoogleMapsCall {
384 id: String,
386 queries: Vec<String>,
388 signature: Option<String>,
390 },
391 GoogleMapsResult {
393 call_id: String,
395 result: Vec<GoogleMapsResultItem>,
397 signature: Option<String>,
399 },
400 Unknown {
406 step_type: String,
408 data: serde_json::Value,
410 },
411}
412
413impl Step {
414 #[must_use]
420 pub const fn is_unknown(&self) -> bool {
421 matches!(self, Self::Unknown { .. })
422 }
423
424 #[must_use]
426 pub fn unknown_step_type(&self) -> Option<&str> {
427 match self {
428 Self::Unknown { step_type, .. } => Some(step_type),
429 _ => None,
430 }
431 }
432
433 #[must_use]
435 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
436 match self {
437 Self::Unknown { data, .. } => Some(data),
438 _ => None,
439 }
440 }
441
442 pub fn user_text(text: impl Into<String>) -> Self {
448 Self::UserInput {
449 content: vec![Content::text(text)],
450 }
451 }
452
453 #[must_use]
455 pub fn user_input(content: Vec<Content>) -> Self {
456 Self::UserInput { content }
457 }
458
459 pub fn model_text(text: impl Into<String>) -> Self {
463 Self::ModelOutput {
464 content: vec![Content::text(text)],
465 error: None,
466 }
467 }
468
469 #[must_use]
471 pub fn model_output(content: Vec<Content>) -> Self {
472 Self::ModelOutput {
473 content,
474 error: None,
475 }
476 }
477
478 pub fn thought(signature: impl Into<String>) -> Self {
480 Self::Thought {
481 signature: Some(signature.into()),
482 summary: Vec::new(),
483 }
484 }
485
486 pub fn function_call(
488 id: impl Into<String>,
489 name: impl Into<String>,
490 arguments: serde_json::Value,
491 ) -> Self {
492 Self::FunctionCall {
493 id: id.into(),
494 name: name.into(),
495 arguments,
496 signature: None,
497 }
498 }
499
500 pub fn function_result(
505 name: impl Into<String>,
506 call_id: impl Into<String>,
507 result: impl Into<FunctionResultPayload>,
508 ) -> Self {
509 Self::FunctionResult {
510 call_id: call_id.into(),
511 name: Some(name.into()),
512 result: result.into(),
513 is_error: None,
514 signature: None,
515 }
516 }
517
518 pub fn function_result_error(
520 name: impl Into<String>,
521 call_id: impl Into<String>,
522 result: impl Into<FunctionResultPayload>,
523 ) -> Self {
524 Self::FunctionResult {
525 call_id: call_id.into(),
526 name: Some(name.into()),
527 result: result.into(),
528 is_error: Some(true),
529 signature: None,
530 }
531 }
532
533 #[must_use]
540 pub fn content(&self) -> Option<&[Content]> {
541 match self {
542 Self::UserInput { content } | Self::ModelOutput { content, .. } => Some(content),
543 _ => None,
544 }
545 }
546
547 #[must_use]
549 pub fn as_text(&self) -> Option<&str> {
550 self.content()?.iter().find_map(Content::as_text)
551 }
552
553 #[must_use]
559 pub fn signature(&self) -> Option<&str> {
560 match self {
561 Self::Thought { signature, .. }
562 | Self::FunctionCall { signature, .. }
563 | Self::FunctionResult { signature, .. }
564 | Self::CodeExecutionCall { signature, .. }
565 | Self::CodeExecutionResult { signature, .. }
566 | Self::UrlContextCall { signature, .. }
567 | Self::UrlContextResult { signature, .. }
568 | Self::GoogleSearchCall { signature, .. }
569 | Self::GoogleSearchResult { signature, .. }
570 | Self::FileSearchCall { signature, .. }
571 | Self::FileSearchResult { signature, .. }
572 | Self::GoogleMapsCall { signature, .. }
573 | Self::GoogleMapsResult { signature, .. } => signature.as_deref(),
574 _ => None,
575 }
576 }
577
578 #[must_use]
580 pub fn step_type(&self) -> &str {
581 match self {
582 Self::UserInput { .. } => "user_input",
583 Self::ModelOutput { .. } => "model_output",
584 Self::Thought { .. } => "thought",
585 Self::FunctionCall { .. } => "function_call",
586 Self::FunctionResult { .. } => "function_result",
587 Self::CodeExecutionCall { .. } => "code_execution_call",
588 Self::CodeExecutionResult { .. } => "code_execution_result",
589 Self::UrlContextCall { .. } => "url_context_call",
590 Self::UrlContextResult { .. } => "url_context_result",
591 Self::GoogleSearchCall { .. } => "google_search_call",
592 Self::GoogleSearchResult { .. } => "google_search_result",
593 Self::McpServerToolCall { .. } => "mcp_server_tool_call",
594 Self::McpServerToolResult { .. } => "mcp_server_tool_result",
595 Self::FileSearchCall { .. } => "file_search_call",
596 Self::FileSearchResult { .. } => "file_search_result",
597 Self::GoogleMapsCall { .. } => "google_maps_call",
598 Self::GoogleMapsResult { .. } => "google_maps_result",
599 Self::Unknown { step_type, .. } => step_type,
600 }
601 }
602}
603
604impl Serialize for Step {
605 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
606 where
607 S: serde::Serializer,
608 {
609 use serde::ser::SerializeMap;
610
611 let mut map = serializer.serialize_map(None)?;
612 match self {
613 Self::UserInput { content } => {
614 map.serialize_entry("type", "user_input")?;
615 map.serialize_entry("content", content)?;
616 }
617 Self::ModelOutput { content, error } => {
618 map.serialize_entry("type", "model_output")?;
619 map.serialize_entry("content", content)?;
620 if let Some(e) = error {
621 map.serialize_entry("error", e)?;
622 }
623 }
624 Self::Thought { signature, summary } => {
625 map.serialize_entry("type", "thought")?;
626 if let Some(s) = signature {
627 map.serialize_entry("signature", s)?;
628 }
629 if !summary.is_empty() {
630 map.serialize_entry("summary", summary)?;
631 }
632 }
633 Self::FunctionCall {
634 id,
635 name,
636 arguments,
637 signature,
638 } => {
639 map.serialize_entry("type", "function_call")?;
640 map.serialize_entry("id", id)?;
641 map.serialize_entry("name", name)?;
642 map.serialize_entry("arguments", arguments)?;
643 if let Some(s) = signature {
644 map.serialize_entry("signature", s)?;
645 }
646 }
647 Self::FunctionResult {
648 call_id,
649 name,
650 result,
651 is_error,
652 signature,
653 } => {
654 map.serialize_entry("type", "function_result")?;
655 map.serialize_entry("call_id", call_id)?;
656 if let Some(n) = name {
657 map.serialize_entry("name", n)?;
658 }
659 map.serialize_entry("result", result)?;
660 if let Some(e) = is_error {
661 map.serialize_entry("is_error", e)?;
662 }
663 if let Some(s) = signature {
664 map.serialize_entry("signature", s)?;
665 }
666 }
667 Self::CodeExecutionCall {
668 id,
669 language,
670 code,
671 signature,
672 } => {
673 map.serialize_entry("type", "code_execution_call")?;
674 map.serialize_entry("id", id)?;
675 map.serialize_entry(
676 "arguments",
677 &serde_json::json!({ "language": language, "code": code }),
678 )?;
679 if let Some(s) = signature {
680 map.serialize_entry("signature", s)?;
681 }
682 }
683 Self::CodeExecutionResult {
684 call_id,
685 result,
686 is_error,
687 signature,
688 } => {
689 map.serialize_entry("type", "code_execution_result")?;
690 map.serialize_entry("call_id", call_id)?;
691 map.serialize_entry("result", result)?;
692 map.serialize_entry("is_error", is_error)?;
693 if let Some(s) = signature {
694 map.serialize_entry("signature", s)?;
695 }
696 }
697 Self::UrlContextCall {
698 id,
699 urls,
700 signature,
701 } => {
702 map.serialize_entry("type", "url_context_call")?;
703 map.serialize_entry("id", id)?;
704 map.serialize_entry("arguments", &serde_json::json!({ "urls": urls }))?;
705 if let Some(s) = signature {
706 map.serialize_entry("signature", s)?;
707 }
708 }
709 Self::UrlContextResult {
710 call_id,
711 result,
712 is_error,
713 signature,
714 } => {
715 map.serialize_entry("type", "url_context_result")?;
716 map.serialize_entry("call_id", call_id)?;
717 map.serialize_entry("result", result)?;
718 if let Some(e) = is_error {
719 map.serialize_entry("is_error", e)?;
720 }
721 if let Some(s) = signature {
722 map.serialize_entry("signature", s)?;
723 }
724 }
725 Self::GoogleSearchCall {
726 id,
727 queries,
728 search_type,
729 signature,
730 } => {
731 map.serialize_entry("type", "google_search_call")?;
732 map.serialize_entry("id", id)?;
733 map.serialize_entry("arguments", &serde_json::json!({ "queries": queries }))?;
734 if let Some(st) = search_type {
735 map.serialize_entry("search_type", st)?;
736 }
737 if let Some(s) = signature {
738 map.serialize_entry("signature", s)?;
739 }
740 }
741 Self::GoogleSearchResult {
742 call_id,
743 result,
744 is_error,
745 signature,
746 } => {
747 map.serialize_entry("type", "google_search_result")?;
748 map.serialize_entry("call_id", call_id)?;
749 map.serialize_entry("result", result)?;
750 if let Some(e) = is_error {
751 map.serialize_entry("is_error", e)?;
752 }
753 if let Some(s) = signature {
754 map.serialize_entry("signature", s)?;
755 }
756 }
757 Self::McpServerToolCall {
758 id,
759 name,
760 server_name,
761 arguments,
762 } => {
763 map.serialize_entry("type", "mcp_server_tool_call")?;
764 map.serialize_entry("id", id)?;
765 map.serialize_entry("name", name)?;
766 map.serialize_entry("server_name", server_name)?;
767 map.serialize_entry("arguments", arguments)?;
768 }
769 Self::McpServerToolResult {
770 call_id,
771 name,
772 server_name,
773 result,
774 } => {
775 map.serialize_entry("type", "mcp_server_tool_result")?;
776 map.serialize_entry("call_id", call_id)?;
777 if let Some(n) = name {
778 map.serialize_entry("name", n)?;
779 }
780 if let Some(sn) = server_name {
781 map.serialize_entry("server_name", sn)?;
782 }
783 map.serialize_entry("result", result)?;
784 }
785 Self::FileSearchCall { id, signature } => {
786 map.serialize_entry("type", "file_search_call")?;
787 map.serialize_entry("id", id)?;
788 if let Some(s) = signature {
789 map.serialize_entry("signature", s)?;
790 }
791 }
792 Self::FileSearchResult {
793 call_id,
794 result,
795 signature,
796 } => {
797 map.serialize_entry("type", "file_search_result")?;
798 map.serialize_entry("call_id", call_id)?;
799 if !result.is_empty() {
800 map.serialize_entry("result", result)?;
801 }
802 if let Some(s) = signature {
803 map.serialize_entry("signature", s)?;
804 }
805 }
806 Self::GoogleMapsCall {
807 id,
808 queries,
809 signature,
810 } => {
811 map.serialize_entry("type", "google_maps_call")?;
812 map.serialize_entry("id", id)?;
813 map.serialize_entry("arguments", &serde_json::json!({ "queries": queries }))?;
814 if let Some(s) = signature {
815 map.serialize_entry("signature", s)?;
816 }
817 }
818 Self::GoogleMapsResult {
819 call_id,
820 result,
821 signature,
822 } => {
823 map.serialize_entry("type", "google_maps_result")?;
824 map.serialize_entry("call_id", call_id)?;
825 map.serialize_entry("result", result)?;
826 if let Some(s) = signature {
827 map.serialize_entry("signature", s)?;
828 }
829 }
830 Self::Unknown { step_type, data } => {
831 map.serialize_entry("type", step_type)?;
832 match data {
833 serde_json::Value::Object(obj) => {
834 for (key, value) in obj {
835 if key != "type" {
836 map.serialize_entry(key, value)?;
837 }
838 }
839 }
840 other if !other.is_null() => {
841 map.serialize_entry("data", other)?;
842 }
843 _ => {}
844 }
845 }
846 }
847 map.end()
848 }
849}
850
851fn string_vec_from_arguments(arguments: Option<&serde_json::Value>, key: &str) -> Vec<String> {
854 arguments
855 .and_then(|args| args.get(key))
856 .and_then(|v| v.as_array())
857 .map(|arr| {
858 arr.iter()
859 .filter_map(|v| v.as_str().map(String::from))
860 .collect()
861 })
862 .unwrap_or_default()
863}
864
865impl<'de> Deserialize<'de> for Step {
866 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
867 where
868 D: serde::Deserializer<'de>,
869 {
870 #[cfg(feature = "strict-unknown")]
871 use serde::de::Error as _;
872
873 let value = serde_json::Value::deserialize(deserializer)?;
874
875 #[derive(Deserialize)]
876 #[serde(tag = "type", rename_all = "snake_case")]
877 enum KnownStep {
878 UserInput {
879 #[serde(default)]
880 content: Vec<Content>,
881 },
882 ModelOutput {
883 #[serde(default)]
884 content: Vec<Content>,
885 #[serde(default)]
886 error: Option<StepError>,
887 },
888 Thought {
889 #[serde(default)]
890 signature: Option<String>,
891 #[serde(default)]
892 summary: Vec<Content>,
893 },
894 FunctionCall {
895 id: String,
896 name: String,
897 #[serde(default)]
898 arguments: serde_json::Value,
899 #[serde(default)]
900 signature: Option<String>,
901 },
902 FunctionResult {
903 call_id: String,
904 #[serde(default)]
905 name: Option<String>,
906 #[serde(default)]
907 result: Option<FunctionResultPayload>,
908 #[serde(default)]
909 is_error: Option<bool>,
910 #[serde(default)]
911 signature: Option<String>,
912 },
913 CodeExecutionCall {
914 id: String,
915 #[serde(default)]
916 arguments: Option<serde_json::Value>,
917 #[serde(default)]
918 signature: Option<String>,
919 },
920 CodeExecutionResult {
921 call_id: String,
922 #[serde(default)]
923 result: String,
924 #[serde(default)]
925 is_error: Option<bool>,
926 #[serde(default)]
927 signature: Option<String>,
928 },
929 UrlContextCall {
930 id: String,
931 #[serde(default)]
932 arguments: Option<serde_json::Value>,
933 #[serde(default)]
934 signature: Option<String>,
935 },
936 UrlContextResult {
937 call_id: String,
938 #[serde(default)]
939 result: Vec<UrlContextResultItem>,
940 #[serde(default)]
941 is_error: Option<bool>,
942 #[serde(default)]
943 signature: Option<String>,
944 },
945 GoogleSearchCall {
946 id: String,
947 #[serde(default)]
948 arguments: Option<serde_json::Value>,
949 #[serde(default)]
950 search_type: Option<crate::tools::SearchType>,
951 #[serde(default)]
952 signature: Option<String>,
953 },
954 GoogleSearchResult {
955 call_id: String,
956 #[serde(default)]
957 result: Vec<GoogleSearchResultItem>,
958 #[serde(default)]
959 is_error: Option<bool>,
960 #[serde(default)]
961 signature: Option<String>,
962 },
963 McpServerToolCall {
964 id: String,
965 name: String,
966 server_name: String,
967 #[serde(default)]
968 arguments: serde_json::Value,
969 },
970 McpServerToolResult {
971 call_id: String,
972 #[serde(default)]
973 name: Option<String>,
974 #[serde(default)]
975 server_name: Option<String>,
976 #[serde(default)]
977 result: Option<FunctionResultPayload>,
978 },
979 FileSearchCall {
980 id: String,
981 #[serde(default)]
982 signature: Option<String>,
983 },
984 FileSearchResult {
985 call_id: String,
986 #[serde(default)]
987 result: Vec<FileSearchResultItem>,
988 #[serde(default)]
989 signature: Option<String>,
990 },
991 GoogleMapsCall {
992 id: String,
993 #[serde(default)]
994 arguments: Option<serde_json::Value>,
995 #[serde(default)]
996 signature: Option<String>,
997 },
998 GoogleMapsResult {
999 call_id: String,
1000 #[serde(default)]
1001 result: Vec<GoogleMapsResultItem>,
1002 #[serde(default)]
1003 signature: Option<String>,
1004 },
1005 }
1006
1007 match serde_json::from_value::<KnownStep>(value.clone()) {
1008 Ok(known) => Ok(match known {
1009 KnownStep::UserInput { content } => Step::UserInput { content },
1010 KnownStep::ModelOutput { content, error } => Step::ModelOutput { content, error },
1011 KnownStep::Thought { signature, summary } => Step::Thought { signature, summary },
1012 KnownStep::FunctionCall {
1013 id,
1014 name,
1015 arguments,
1016 signature,
1017 } => Step::FunctionCall {
1018 id,
1019 name,
1020 arguments,
1021 signature,
1022 },
1023 KnownStep::FunctionResult {
1024 call_id,
1025 name,
1026 result,
1027 is_error,
1028 signature,
1029 } => Step::FunctionResult {
1030 call_id,
1031 name,
1032 result: result.unwrap_or(FunctionResultPayload::Json(serde_json::Value::Null)),
1033 is_error,
1034 signature,
1035 },
1036 KnownStep::CodeExecutionCall {
1037 id,
1038 arguments,
1039 signature,
1040 } => {
1041 let language = arguments
1042 .as_ref()
1043 .and_then(|a| a.get("language"))
1044 .and_then(|l| {
1045 serde_json::from_value::<CodeExecutionLanguage>(l.clone()).ok()
1046 })
1047 .unwrap_or_default();
1048 let code = arguments
1049 .as_ref()
1050 .and_then(|a| a.get("code"))
1051 .and_then(|c| c.as_str())
1052 .unwrap_or_default()
1053 .to_string();
1054 Step::CodeExecutionCall {
1055 id,
1056 language,
1057 code,
1058 signature,
1059 }
1060 }
1061 KnownStep::CodeExecutionResult {
1062 call_id,
1063 result,
1064 is_error,
1065 signature,
1066 } => Step::CodeExecutionResult {
1067 call_id,
1068 result,
1069 is_error: is_error.unwrap_or(false),
1070 signature,
1071 },
1072 KnownStep::UrlContextCall {
1073 id,
1074 arguments,
1075 signature,
1076 } => Step::UrlContextCall {
1077 id,
1078 urls: string_vec_from_arguments(arguments.as_ref(), "urls"),
1079 signature,
1080 },
1081 KnownStep::UrlContextResult {
1082 call_id,
1083 result,
1084 is_error,
1085 signature,
1086 } => Step::UrlContextResult {
1087 call_id,
1088 result,
1089 is_error,
1090 signature,
1091 },
1092 KnownStep::GoogleSearchCall {
1093 id,
1094 arguments,
1095 search_type,
1096 signature,
1097 } => Step::GoogleSearchCall {
1098 id,
1099 queries: string_vec_from_arguments(arguments.as_ref(), "queries"),
1100 search_type,
1101 signature,
1102 },
1103 KnownStep::GoogleSearchResult {
1104 call_id,
1105 result,
1106 is_error,
1107 signature,
1108 } => Step::GoogleSearchResult {
1109 call_id,
1110 result,
1111 is_error,
1112 signature,
1113 },
1114 KnownStep::McpServerToolCall {
1115 id,
1116 name,
1117 server_name,
1118 arguments,
1119 } => Step::McpServerToolCall {
1120 id,
1121 name,
1122 server_name,
1123 arguments,
1124 },
1125 KnownStep::McpServerToolResult {
1126 call_id,
1127 name,
1128 server_name,
1129 result,
1130 } => Step::McpServerToolResult {
1131 call_id,
1132 name,
1133 server_name,
1134 result: result.unwrap_or(FunctionResultPayload::Json(serde_json::Value::Null)),
1135 },
1136 KnownStep::FileSearchCall { id, signature } => {
1137 Step::FileSearchCall { id, signature }
1138 }
1139 KnownStep::FileSearchResult {
1140 call_id,
1141 result,
1142 signature,
1143 } => Step::FileSearchResult {
1144 call_id,
1145 result,
1146 signature,
1147 },
1148 KnownStep::GoogleMapsCall {
1149 id,
1150 arguments,
1151 signature,
1152 } => Step::GoogleMapsCall {
1153 id,
1154 queries: string_vec_from_arguments(arguments.as_ref(), "queries"),
1155 signature,
1156 },
1157 KnownStep::GoogleMapsResult {
1158 call_id,
1159 result,
1160 signature,
1161 } => Step::GoogleMapsResult {
1162 call_id,
1163 result,
1164 signature,
1165 },
1166 }),
1167 Err(parse_error) => {
1168 let step_type = value
1169 .get("type")
1170 .and_then(|v| v.as_str())
1171 .unwrap_or("<missing type>")
1172 .to_string();
1173
1174 tracing::warn!(
1175 "Encountered unknown Step type '{}'. Parse error: {}. \
1176 This may indicate a new API feature or a malformed response. \
1177 The step will be preserved in the Unknown variant.",
1178 step_type,
1179 parse_error
1180 );
1181
1182 #[cfg(feature = "strict-unknown")]
1183 {
1184 Err(D::Error::custom(format!(
1185 "Unknown Step type '{}'. \
1186 Strict mode is enabled via the 'strict-unknown' feature flag. \
1187 Either update the library or disable strict mode.",
1188 step_type
1189 )))
1190 }
1191
1192 #[cfg(not(feature = "strict-unknown"))]
1193 {
1194 Ok(Step::Unknown {
1195 step_type,
1196 data: value,
1197 })
1198 }
1199 }
1200 }
1201 }
1202}
1203
1204#[derive(Clone, Debug, PartialEq)]
1221#[non_exhaustive]
1222pub enum StepDelta {
1223 Text {
1225 text: String,
1227 },
1228 Image {
1230 data: Option<String>,
1232 uri: Option<String>,
1234 mime_type: Option<String>,
1236 resolution: Option<Resolution>,
1238 },
1239 Audio {
1241 data: Option<String>,
1243 uri: Option<String>,
1245 mime_type: Option<String>,
1247 rate: Option<u32>,
1249 sample_rate: Option<u32>,
1251 channels: Option<u32>,
1253 },
1254 Video {
1256 data: Option<String>,
1258 uri: Option<String>,
1260 mime_type: Option<String>,
1262 resolution: Option<Resolution>,
1264 },
1265 Document {
1267 data: Option<String>,
1269 uri: Option<String>,
1271 mime_type: Option<String>,
1273 },
1274 ThoughtSummary {
1276 content: Option<Content>,
1278 },
1279 ThoughtSignature {
1281 signature: Option<String>,
1283 },
1284 TextAnnotation {
1286 annotations: Vec<Annotation>,
1288 },
1289 ArgumentsDelta {
1294 arguments: String,
1296 },
1297 FunctionResult {
1299 call_id: String,
1301 name: Option<String>,
1303 result: FunctionResultPayload,
1305 is_error: Option<bool>,
1307 },
1308 CodeExecutionCall {
1310 language: Option<CodeExecutionLanguage>,
1312 code: Option<String>,
1314 signature: Option<String>,
1316 },
1317 CodeExecutionResult {
1319 result: String,
1321 is_error: Option<bool>,
1323 signature: Option<String>,
1325 },
1326 UrlContextCall {
1328 urls: Vec<String>,
1330 signature: Option<String>,
1332 },
1333 UrlContextResult {
1335 result: Vec<UrlContextResultItem>,
1337 is_error: Option<bool>,
1339 signature: Option<String>,
1341 },
1342 GoogleSearchCall {
1344 queries: Vec<String>,
1346 signature: Option<String>,
1348 },
1349 GoogleSearchResult {
1351 result: Vec<GoogleSearchResultItem>,
1353 is_error: Option<bool>,
1355 signature: Option<String>,
1357 },
1358 McpServerToolCall {
1360 name: String,
1362 server_name: String,
1364 arguments: serde_json::Value,
1366 },
1367 McpServerToolResult {
1369 name: Option<String>,
1371 server_name: Option<String>,
1373 result: FunctionResultPayload,
1375 },
1376 FileSearchCall {
1378 signature: Option<String>,
1380 },
1381 FileSearchResult {
1383 result: Vec<FileSearchResultItem>,
1385 signature: Option<String>,
1387 },
1388 GoogleMapsCall {
1390 queries: Vec<String>,
1392 signature: Option<String>,
1394 },
1395 GoogleMapsResult {
1397 result: Vec<GoogleMapsResultItem>,
1399 signature: Option<String>,
1401 },
1402 Unknown {
1404 delta_type: String,
1406 data: serde_json::Value,
1408 },
1409}
1410
1411impl StepDelta {
1412 #[must_use]
1414 pub const fn is_unknown(&self) -> bool {
1415 matches!(self, Self::Unknown { .. })
1416 }
1417
1418 #[must_use]
1420 pub fn unknown_delta_type(&self) -> Option<&str> {
1421 match self {
1422 Self::Unknown { delta_type, .. } => Some(delta_type),
1423 _ => None,
1424 }
1425 }
1426
1427 #[must_use]
1429 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
1430 match self {
1431 Self::Unknown { data, .. } => Some(data),
1432 _ => None,
1433 }
1434 }
1435
1436 #[must_use]
1438 pub fn as_text(&self) -> Option<&str> {
1439 match self {
1440 Self::Text { text } => Some(text),
1441 _ => None,
1442 }
1443 }
1444
1445 #[must_use]
1447 pub fn as_arguments_delta(&self) -> Option<&str> {
1448 match self {
1449 Self::ArgumentsDelta { arguments } => Some(arguments),
1450 _ => None,
1451 }
1452 }
1453}
1454
1455impl Serialize for StepDelta {
1456 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1457 where
1458 S: serde::Serializer,
1459 {
1460 use serde::ser::SerializeMap;
1461
1462 macro_rules! opt_entry {
1464 ($map:expr, $key:literal, $val:expr) => {
1465 if let Some(v) = $val {
1466 $map.serialize_entry($key, v)?;
1467 }
1468 };
1469 }
1470
1471 let mut map = serializer.serialize_map(None)?;
1472 match self {
1473 Self::Text { text } => {
1474 map.serialize_entry("type", "text")?;
1475 map.serialize_entry("text", text)?;
1476 }
1477 Self::Image {
1478 data,
1479 uri,
1480 mime_type,
1481 resolution,
1482 } => {
1483 map.serialize_entry("type", "image")?;
1484 opt_entry!(map, "data", data);
1485 opt_entry!(map, "uri", uri);
1486 opt_entry!(map, "mime_type", mime_type);
1487 opt_entry!(map, "resolution", resolution);
1488 }
1489 Self::Audio {
1490 data,
1491 uri,
1492 mime_type,
1493 rate,
1494 sample_rate,
1495 channels,
1496 } => {
1497 map.serialize_entry("type", "audio")?;
1498 opt_entry!(map, "data", data);
1499 opt_entry!(map, "uri", uri);
1500 opt_entry!(map, "mime_type", mime_type);
1501 opt_entry!(map, "rate", rate);
1502 opt_entry!(map, "sample_rate", sample_rate);
1503 opt_entry!(map, "channels", channels);
1504 }
1505 Self::Video {
1506 data,
1507 uri,
1508 mime_type,
1509 resolution,
1510 } => {
1511 map.serialize_entry("type", "video")?;
1512 opt_entry!(map, "data", data);
1513 opt_entry!(map, "uri", uri);
1514 opt_entry!(map, "mime_type", mime_type);
1515 opt_entry!(map, "resolution", resolution);
1516 }
1517 Self::Document {
1518 data,
1519 uri,
1520 mime_type,
1521 } => {
1522 map.serialize_entry("type", "document")?;
1523 opt_entry!(map, "data", data);
1524 opt_entry!(map, "uri", uri);
1525 opt_entry!(map, "mime_type", mime_type);
1526 }
1527 Self::ThoughtSummary { content } => {
1528 map.serialize_entry("type", "thought_summary")?;
1529 opt_entry!(map, "content", content);
1530 }
1531 Self::ThoughtSignature { signature } => {
1532 map.serialize_entry("type", "thought_signature")?;
1533 opt_entry!(map, "signature", signature);
1534 }
1535 Self::TextAnnotation { annotations } => {
1536 map.serialize_entry("type", "text_annotation_delta")?;
1537 map.serialize_entry("annotations", annotations)?;
1538 }
1539 Self::ArgumentsDelta { arguments } => {
1540 map.serialize_entry("type", "arguments_delta")?;
1541 map.serialize_entry("arguments", arguments)?;
1542 }
1543 Self::FunctionResult {
1544 call_id,
1545 name,
1546 result,
1547 is_error,
1548 } => {
1549 map.serialize_entry("type", "function_result")?;
1550 map.serialize_entry("call_id", call_id)?;
1551 opt_entry!(map, "name", name);
1552 map.serialize_entry("result", result)?;
1553 opt_entry!(map, "is_error", is_error);
1554 }
1555 Self::CodeExecutionCall {
1556 language,
1557 code,
1558 signature,
1559 } => {
1560 map.serialize_entry("type", "code_execution_call")?;
1561 map.serialize_entry(
1562 "arguments",
1563 &serde_json::json!({ "language": language, "code": code }),
1564 )?;
1565 opt_entry!(map, "signature", signature);
1566 }
1567 Self::CodeExecutionResult {
1568 result,
1569 is_error,
1570 signature,
1571 } => {
1572 map.serialize_entry("type", "code_execution_result")?;
1573 map.serialize_entry("result", result)?;
1574 opt_entry!(map, "is_error", is_error);
1575 opt_entry!(map, "signature", signature);
1576 }
1577 Self::UrlContextCall { urls, signature } => {
1578 map.serialize_entry("type", "url_context_call")?;
1579 map.serialize_entry("arguments", &serde_json::json!({ "urls": urls }))?;
1580 opt_entry!(map, "signature", signature);
1581 }
1582 Self::UrlContextResult {
1583 result,
1584 is_error,
1585 signature,
1586 } => {
1587 map.serialize_entry("type", "url_context_result")?;
1588 map.serialize_entry("result", result)?;
1589 opt_entry!(map, "is_error", is_error);
1590 opt_entry!(map, "signature", signature);
1591 }
1592 Self::GoogleSearchCall { queries, signature } => {
1593 map.serialize_entry("type", "google_search_call")?;
1594 map.serialize_entry("arguments", &serde_json::json!({ "queries": queries }))?;
1595 opt_entry!(map, "signature", signature);
1596 }
1597 Self::GoogleSearchResult {
1598 result,
1599 is_error,
1600 signature,
1601 } => {
1602 map.serialize_entry("type", "google_search_result")?;
1603 map.serialize_entry("result", result)?;
1604 opt_entry!(map, "is_error", is_error);
1605 opt_entry!(map, "signature", signature);
1606 }
1607 Self::McpServerToolCall {
1608 name,
1609 server_name,
1610 arguments,
1611 } => {
1612 map.serialize_entry("type", "mcp_server_tool_call")?;
1613 map.serialize_entry("name", name)?;
1614 map.serialize_entry("server_name", server_name)?;
1615 map.serialize_entry("arguments", arguments)?;
1616 }
1617 Self::McpServerToolResult {
1618 name,
1619 server_name,
1620 result,
1621 } => {
1622 map.serialize_entry("type", "mcp_server_tool_result")?;
1623 opt_entry!(map, "name", name);
1624 opt_entry!(map, "server_name", server_name);
1625 map.serialize_entry("result", result)?;
1626 }
1627 Self::FileSearchCall { signature } => {
1628 map.serialize_entry("type", "file_search_call")?;
1629 opt_entry!(map, "signature", signature);
1630 }
1631 Self::FileSearchResult { result, signature } => {
1632 map.serialize_entry("type", "file_search_result")?;
1633 map.serialize_entry("result", result)?;
1634 opt_entry!(map, "signature", signature);
1635 }
1636 Self::GoogleMapsCall { queries, signature } => {
1637 map.serialize_entry("type", "google_maps_call")?;
1638 map.serialize_entry("arguments", &serde_json::json!({ "queries": queries }))?;
1639 opt_entry!(map, "signature", signature);
1640 }
1641 Self::GoogleMapsResult { result, signature } => {
1642 map.serialize_entry("type", "google_maps_result")?;
1643 map.serialize_entry("result", result)?;
1644 opt_entry!(map, "signature", signature);
1645 }
1646 Self::Unknown { delta_type, data } => {
1647 map.serialize_entry("type", delta_type)?;
1648 match data {
1649 serde_json::Value::Object(obj) => {
1650 for (key, value) in obj {
1651 if key != "type" {
1652 map.serialize_entry(key, value)?;
1653 }
1654 }
1655 }
1656 other if !other.is_null() => {
1657 map.serialize_entry("data", other)?;
1658 }
1659 _ => {}
1660 }
1661 }
1662 }
1663 map.end()
1664 }
1665}
1666
1667impl<'de> Deserialize<'de> for StepDelta {
1668 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1669 where
1670 D: serde::Deserializer<'de>,
1671 {
1672 let value = serde_json::Value::deserialize(deserializer)?;
1673
1674 #[derive(Deserialize)]
1675 #[serde(tag = "type", rename_all = "snake_case")]
1676 enum KnownDelta {
1677 Text {
1678 #[serde(default)]
1679 text: String,
1680 },
1681 Image {
1682 #[serde(default)]
1683 data: Option<String>,
1684 #[serde(default)]
1685 uri: Option<String>,
1686 #[serde(default)]
1687 mime_type: Option<String>,
1688 #[serde(default)]
1689 resolution: Option<Resolution>,
1690 },
1691 Audio {
1692 #[serde(default)]
1693 data: Option<String>,
1694 #[serde(default)]
1695 uri: Option<String>,
1696 #[serde(default)]
1697 mime_type: Option<String>,
1698 #[serde(default)]
1699 rate: Option<u32>,
1700 #[serde(default)]
1701 sample_rate: Option<u32>,
1702 #[serde(default)]
1703 channels: Option<u32>,
1704 },
1705 Video {
1706 #[serde(default)]
1707 data: Option<String>,
1708 #[serde(default)]
1709 uri: Option<String>,
1710 #[serde(default)]
1711 mime_type: Option<String>,
1712 #[serde(default)]
1713 resolution: Option<Resolution>,
1714 },
1715 Document {
1716 #[serde(default)]
1717 data: Option<String>,
1718 #[serde(default)]
1719 uri: Option<String>,
1720 #[serde(default)]
1721 mime_type: Option<String>,
1722 },
1723 ThoughtSummary {
1724 #[serde(default)]
1725 content: Option<Content>,
1726 },
1727 ThoughtSignature {
1728 #[serde(default)]
1729 signature: Option<String>,
1730 },
1731 #[serde(rename = "text_annotation_delta")]
1732 TextAnnotation {
1733 #[serde(default)]
1734 annotations: Vec<Annotation>,
1735 },
1736 ArgumentsDelta {
1737 #[serde(default)]
1738 arguments: String,
1739 },
1740 FunctionResult {
1741 call_id: String,
1742 #[serde(default)]
1743 name: Option<String>,
1744 #[serde(default)]
1745 result: Option<FunctionResultPayload>,
1746 #[serde(default)]
1747 is_error: Option<bool>,
1748 },
1749 CodeExecutionCall {
1750 #[serde(default)]
1751 arguments: Option<serde_json::Value>,
1752 #[serde(default)]
1753 signature: Option<String>,
1754 },
1755 CodeExecutionResult {
1756 #[serde(default)]
1757 result: String,
1758 #[serde(default)]
1759 is_error: Option<bool>,
1760 #[serde(default)]
1761 signature: Option<String>,
1762 },
1763 UrlContextCall {
1764 #[serde(default)]
1765 arguments: Option<serde_json::Value>,
1766 #[serde(default)]
1767 signature: Option<String>,
1768 },
1769 UrlContextResult {
1770 #[serde(default)]
1771 result: Vec<UrlContextResultItem>,
1772 #[serde(default)]
1773 is_error: Option<bool>,
1774 #[serde(default)]
1775 signature: Option<String>,
1776 },
1777 GoogleSearchCall {
1778 #[serde(default)]
1779 arguments: Option<serde_json::Value>,
1780 #[serde(default)]
1781 signature: Option<String>,
1782 },
1783 GoogleSearchResult {
1784 #[serde(default)]
1785 result: Vec<GoogleSearchResultItem>,
1786 #[serde(default)]
1787 is_error: Option<bool>,
1788 #[serde(default)]
1789 signature: Option<String>,
1790 },
1791 McpServerToolCall {
1792 name: String,
1793 server_name: String,
1794 #[serde(default)]
1795 arguments: serde_json::Value,
1796 },
1797 McpServerToolResult {
1798 #[serde(default)]
1799 name: Option<String>,
1800 #[serde(default)]
1801 server_name: Option<String>,
1802 #[serde(default)]
1803 result: Option<FunctionResultPayload>,
1804 },
1805 FileSearchCall {
1806 #[serde(default)]
1807 signature: Option<String>,
1808 },
1809 FileSearchResult {
1810 #[serde(default)]
1811 result: Vec<FileSearchResultItem>,
1812 #[serde(default)]
1813 signature: Option<String>,
1814 },
1815 GoogleMapsCall {
1816 #[serde(default)]
1817 arguments: Option<serde_json::Value>,
1818 #[serde(default)]
1819 signature: Option<String>,
1820 },
1821 GoogleMapsResult {
1822 #[serde(default)]
1823 result: Vec<GoogleMapsResultItem>,
1824 #[serde(default)]
1825 signature: Option<String>,
1826 },
1827 }
1828
1829 match serde_json::from_value::<KnownDelta>(value.clone()) {
1830 Ok(known) => Ok(match known {
1831 KnownDelta::Text { text } => StepDelta::Text { text },
1832 KnownDelta::Image {
1833 data,
1834 uri,
1835 mime_type,
1836 resolution,
1837 } => StepDelta::Image {
1838 data,
1839 uri,
1840 mime_type,
1841 resolution,
1842 },
1843 KnownDelta::Audio {
1844 data,
1845 uri,
1846 mime_type,
1847 rate,
1848 sample_rate,
1849 channels,
1850 } => StepDelta::Audio {
1851 data,
1852 uri,
1853 mime_type,
1854 rate,
1855 sample_rate,
1856 channels,
1857 },
1858 KnownDelta::Video {
1859 data,
1860 uri,
1861 mime_type,
1862 resolution,
1863 } => StepDelta::Video {
1864 data,
1865 uri,
1866 mime_type,
1867 resolution,
1868 },
1869 KnownDelta::Document {
1870 data,
1871 uri,
1872 mime_type,
1873 } => StepDelta::Document {
1874 data,
1875 uri,
1876 mime_type,
1877 },
1878 KnownDelta::ThoughtSummary { content } => StepDelta::ThoughtSummary { content },
1879 KnownDelta::ThoughtSignature { signature } => {
1880 StepDelta::ThoughtSignature { signature }
1881 }
1882 KnownDelta::TextAnnotation { annotations } => {
1883 StepDelta::TextAnnotation { annotations }
1884 }
1885 KnownDelta::ArgumentsDelta { arguments } => StepDelta::ArgumentsDelta { arguments },
1886 KnownDelta::FunctionResult {
1887 call_id,
1888 name,
1889 result,
1890 is_error,
1891 } => StepDelta::FunctionResult {
1892 call_id,
1893 name,
1894 result: result.unwrap_or(FunctionResultPayload::Json(serde_json::Value::Null)),
1895 is_error,
1896 },
1897 KnownDelta::CodeExecutionCall {
1898 arguments,
1899 signature,
1900 } => {
1901 StepDelta::CodeExecutionCall {
1902 language: arguments.as_ref().and_then(|a| a.get("language")).and_then(
1903 |l| serde_json::from_value::<CodeExecutionLanguage>(l.clone()).ok(),
1904 ),
1905 code: arguments
1906 .as_ref()
1907 .and_then(|a| a.get("code"))
1908 .and_then(|c| c.as_str())
1909 .map(String::from),
1910 signature,
1911 }
1912 }
1913 KnownDelta::CodeExecutionResult {
1914 result,
1915 is_error,
1916 signature,
1917 } => StepDelta::CodeExecutionResult {
1918 result,
1919 is_error,
1920 signature,
1921 },
1922 KnownDelta::UrlContextCall {
1923 arguments,
1924 signature,
1925 } => StepDelta::UrlContextCall {
1926 urls: string_vec_from_arguments(arguments.as_ref(), "urls"),
1927 signature,
1928 },
1929 KnownDelta::UrlContextResult {
1930 result,
1931 is_error,
1932 signature,
1933 } => StepDelta::UrlContextResult {
1934 result,
1935 is_error,
1936 signature,
1937 },
1938 KnownDelta::GoogleSearchCall {
1939 arguments,
1940 signature,
1941 } => StepDelta::GoogleSearchCall {
1942 queries: string_vec_from_arguments(arguments.as_ref(), "queries"),
1943 signature,
1944 },
1945 KnownDelta::GoogleSearchResult {
1946 result,
1947 is_error,
1948 signature,
1949 } => StepDelta::GoogleSearchResult {
1950 result,
1951 is_error,
1952 signature,
1953 },
1954 KnownDelta::McpServerToolCall {
1955 name,
1956 server_name,
1957 arguments,
1958 } => StepDelta::McpServerToolCall {
1959 name,
1960 server_name,
1961 arguments,
1962 },
1963 KnownDelta::McpServerToolResult {
1964 name,
1965 server_name,
1966 result,
1967 } => StepDelta::McpServerToolResult {
1968 name,
1969 server_name,
1970 result: result.unwrap_or(FunctionResultPayload::Json(serde_json::Value::Null)),
1971 },
1972 KnownDelta::FileSearchCall { signature } => StepDelta::FileSearchCall { signature },
1973 KnownDelta::FileSearchResult { result, signature } => {
1974 StepDelta::FileSearchResult { result, signature }
1975 }
1976 KnownDelta::GoogleMapsCall {
1977 arguments,
1978 signature,
1979 } => StepDelta::GoogleMapsCall {
1980 queries: string_vec_from_arguments(arguments.as_ref(), "queries"),
1981 signature,
1982 },
1983 KnownDelta::GoogleMapsResult { result, signature } => {
1984 StepDelta::GoogleMapsResult { result, signature }
1985 }
1986 }),
1987 Err(parse_error) => {
1988 let delta_type = value
1989 .get("type")
1990 .and_then(|v| v.as_str())
1991 .unwrap_or("<missing type>")
1992 .to_string();
1993
1994 tracing::warn!(
1995 "Encountered unknown StepDelta type '{}'. Parse error: {}. \
1996 The delta will be preserved in the Unknown variant.",
1997 delta_type,
1998 parse_error
1999 );
2000
2001 Ok(StepDelta::Unknown {
2002 delta_type,
2003 data: value,
2004 })
2005 }
2006 }
2007 }
2008}
2009
2010#[derive(Debug, Default)]
2019pub(crate) struct StepAccumulator {
2020 steps: std::collections::BTreeMap<usize, AccumulatedStep>,
2021 last_cumulative_usage: Option<crate::response::UsageMetadata>,
2024}
2025
2026#[derive(Debug)]
2027struct AccumulatedStep {
2028 step: Step,
2029 args_buffer: String,
2031}
2032
2033impl StepAccumulator {
2034 pub(crate) fn new() -> Self {
2035 Self::default()
2036 }
2037
2038 pub(crate) fn is_empty(&self) -> bool {
2039 self.steps.is_empty()
2040 }
2041
2042 pub(crate) fn start(&mut self, index: usize, step: Step) {
2044 self.steps.insert(
2045 index,
2046 AccumulatedStep {
2047 step,
2048 args_buffer: String::new(),
2049 },
2050 );
2051 }
2052
2053 pub(crate) fn apply_delta(&mut self, index: usize, delta: &StepDelta) {
2055 let entry = self.steps.entry(index).or_insert_with(|| AccumulatedStep {
2056 step: Step::ModelOutput {
2059 content: Vec::new(),
2060 error: None,
2061 },
2062 args_buffer: String::new(),
2063 });
2064
2065 match delta {
2066 StepDelta::Text { text } => {
2067 if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2068 &mut entry.step
2069 {
2070 if let Some(Content::Text { text: Some(t), .. }) = content.last_mut() {
2071 t.push_str(text);
2072 } else {
2073 content.push(Content::text(text.clone()));
2074 }
2075 }
2076 }
2077 StepDelta::TextAnnotation { annotations } => {
2078 if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2079 &mut entry.step
2080 && let Some(Content::Text {
2081 annotations: annots,
2082 ..
2083 }) = content.last_mut()
2084 {
2085 annots
2086 .get_or_insert_with(Vec::new)
2087 .extend(annotations.iter().cloned());
2088 }
2089 }
2090 StepDelta::Image {
2091 data,
2092 uri,
2093 mime_type,
2094 resolution,
2095 } => {
2096 if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2097 &mut entry.step
2098 {
2099 if let (
2102 Some(Content::Image {
2103 data: Some(existing),
2104 ..
2105 }),
2106 Some(new_data),
2107 ) = (content.last_mut(), data.as_ref())
2108 {
2109 existing.push_str(new_data);
2110 } else {
2111 content.push(Content::Image {
2112 data: data.clone(),
2113 uri: uri.clone(),
2114 mime_type: mime_type.clone(),
2115 resolution: resolution.clone(),
2116 });
2117 }
2118 }
2119 }
2120 StepDelta::Audio {
2121 data,
2122 uri,
2123 mime_type,
2124 rate,
2125 sample_rate,
2126 channels,
2127 } => {
2128 if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2129 &mut entry.step
2130 {
2131 if let (
2134 Some(Content::Audio {
2135 data: Some(existing),
2136 ..
2137 }),
2138 Some(new_data),
2139 ) = (content.last_mut(), data.as_ref())
2140 {
2141 existing.push_str(new_data);
2142 } else {
2143 content.push(Content::Audio {
2144 data: data.clone(),
2145 uri: uri.clone(),
2146 mime_type: mime_type.clone(),
2147 sample_rate: sample_rate.or(*rate),
2148 channels: *channels,
2149 });
2150 }
2151 }
2152 }
2153 StepDelta::Video {
2154 data,
2155 uri,
2156 mime_type,
2157 resolution,
2158 } => {
2159 if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2160 &mut entry.step
2161 {
2162 if let (
2165 Some(Content::Video {
2166 data: Some(existing),
2167 ..
2168 }),
2169 Some(new_data),
2170 ) = (content.last_mut(), data.as_ref())
2171 {
2172 existing.push_str(new_data);
2173 } else {
2174 content.push(Content::Video {
2175 data: data.clone(),
2176 uri: uri.clone(),
2177 mime_type: mime_type.clone(),
2178 resolution: resolution.clone(),
2179 });
2180 }
2181 }
2182 }
2183 StepDelta::Document {
2184 data,
2185 uri,
2186 mime_type,
2187 } => {
2188 if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2189 &mut entry.step
2190 {
2191 if let (
2194 Some(Content::Document {
2195 data: Some(existing),
2196 ..
2197 }),
2198 Some(new_data),
2199 ) = (content.last_mut(), data.as_ref())
2200 {
2201 existing.push_str(new_data);
2202 } else {
2203 content.push(Content::Document {
2204 data: data.clone(),
2205 uri: uri.clone(),
2206 mime_type: mime_type.clone(),
2207 });
2208 }
2209 }
2210 }
2211 StepDelta::ThoughtSummary { content } => {
2212 if let Step::Thought { summary, .. } = &mut entry.step
2213 && let Some(c) = content
2214 {
2215 if let (
2217 Some(Content::Text { text: Some(t), .. }),
2218 Content::Text {
2219 text: Some(new), ..
2220 },
2221 ) = (summary.last_mut(), c)
2222 {
2223 t.push_str(new);
2224 } else {
2225 summary.push(c.clone());
2226 }
2227 }
2228 }
2229 StepDelta::ThoughtSignature { signature } => {
2230 if let Step::Thought { signature: sig, .. } = &mut entry.step
2231 && let Some(fragment) = signature
2232 {
2233 sig.get_or_insert_with(String::new).push_str(fragment);
2234 }
2235 }
2236 StepDelta::ArgumentsDelta { arguments } => {
2237 entry.args_buffer.push_str(arguments);
2238 }
2239 StepDelta::FunctionResult {
2240 call_id,
2241 name,
2242 result,
2243 is_error,
2244 } => {
2245 let signature = match &entry.step {
2248 Step::FunctionResult { signature, .. } => signature.clone(),
2249 _ => None,
2250 };
2251 entry.step = Step::FunctionResult {
2252 call_id: call_id.clone(),
2253 name: name.clone(),
2254 result: result.clone(),
2255 is_error: *is_error,
2256 signature,
2257 };
2258 }
2259 StepDelta::CodeExecutionCall {
2260 language,
2261 code,
2262 signature,
2263 } => {
2264 if let Step::CodeExecutionCall {
2265 language: lang,
2266 code: existing_code,
2267 signature: sig,
2268 ..
2269 } = &mut entry.step
2270 {
2271 if let Some(l) = language {
2272 *lang = l.clone();
2273 }
2274 if let Some(c) = code {
2275 existing_code.push_str(c);
2276 }
2277 if signature.is_some() {
2278 *sig = signature.clone();
2279 }
2280 }
2281 }
2282 StepDelta::CodeExecutionResult {
2283 result,
2284 is_error,
2285 signature,
2286 } => {
2287 if let Step::CodeExecutionResult {
2288 result: existing,
2289 is_error: err,
2290 signature: sig,
2291 ..
2292 } = &mut entry.step
2293 {
2294 existing.push_str(result);
2295 if let Some(e) = is_error {
2296 *err = *e;
2297 }
2298 if signature.is_some() {
2299 *sig = signature.clone();
2300 }
2301 }
2302 }
2303 StepDelta::UrlContextCall { urls, signature } => {
2304 if let Step::UrlContextCall {
2305 urls: existing,
2306 signature: sig,
2307 ..
2308 } = &mut entry.step
2309 {
2310 existing.extend(urls.iter().cloned());
2311 if signature.is_some() {
2312 *sig = signature.clone();
2313 }
2314 }
2315 }
2316 StepDelta::UrlContextResult {
2317 result,
2318 is_error,
2319 signature,
2320 } => {
2321 if let Step::UrlContextResult {
2322 result: existing,
2323 is_error: err,
2324 signature: sig,
2325 ..
2326 } = &mut entry.step
2327 {
2328 existing.extend(result.iter().cloned());
2329 if is_error.is_some() {
2330 *err = *is_error;
2331 }
2332 if signature.is_some() {
2333 *sig = signature.clone();
2334 }
2335 }
2336 }
2337 StepDelta::GoogleSearchCall { queries, signature } => {
2338 if let Step::GoogleSearchCall {
2339 queries: existing,
2340 signature: sig,
2341 ..
2342 } = &mut entry.step
2343 {
2344 existing.extend(queries.iter().cloned());
2345 if signature.is_some() {
2346 *sig = signature.clone();
2347 }
2348 }
2349 }
2350 StepDelta::GoogleSearchResult {
2351 result,
2352 is_error,
2353 signature,
2354 } => {
2355 if let Step::GoogleSearchResult {
2356 result: existing,
2357 is_error: err,
2358 signature: sig,
2359 ..
2360 } = &mut entry.step
2361 {
2362 existing.extend(result.iter().cloned());
2363 if is_error.is_some() {
2364 *err = *is_error;
2365 }
2366 if signature.is_some() {
2367 *sig = signature.clone();
2368 }
2369 }
2370 }
2371 StepDelta::McpServerToolCall {
2372 name,
2373 server_name,
2374 arguments,
2375 } => {
2376 if let Step::McpServerToolCall {
2377 name: n,
2378 server_name: sn,
2379 arguments: args,
2380 ..
2381 } = &mut entry.step
2382 {
2383 *n = name.clone();
2384 *sn = server_name.clone();
2385 *args = arguments.clone();
2386 }
2387 }
2388 StepDelta::McpServerToolResult {
2389 name,
2390 server_name,
2391 result,
2392 } => {
2393 if let Step::McpServerToolResult {
2394 name: n,
2395 server_name: sn,
2396 result: r,
2397 ..
2398 } = &mut entry.step
2399 {
2400 if name.is_some() {
2401 *n = name.clone();
2402 }
2403 if server_name.is_some() {
2404 *sn = server_name.clone();
2405 }
2406 *r = result.clone();
2407 }
2408 }
2409 StepDelta::FileSearchCall { signature } => {
2410 if let Step::FileSearchCall { signature: sig, .. } = &mut entry.step
2411 && signature.is_some()
2412 {
2413 *sig = signature.clone();
2414 }
2415 }
2416 StepDelta::FileSearchResult { result, signature } => {
2417 if let Step::FileSearchResult {
2418 result: existing,
2419 signature: sig,
2420 ..
2421 } = &mut entry.step
2422 {
2423 existing.extend(result.iter().cloned());
2424 if signature.is_some() {
2425 *sig = signature.clone();
2426 }
2427 }
2428 }
2429 StepDelta::GoogleMapsCall { queries, signature } => {
2430 if let Step::GoogleMapsCall {
2431 queries: existing,
2432 signature: sig,
2433 ..
2434 } = &mut entry.step
2435 {
2436 existing.extend(queries.iter().cloned());
2437 if signature.is_some() {
2438 *sig = signature.clone();
2439 }
2440 }
2441 }
2442 StepDelta::GoogleMapsResult { result, signature } => {
2443 if let Step::GoogleMapsResult {
2444 result: existing,
2445 signature: sig,
2446 ..
2447 } = &mut entry.step
2448 {
2449 existing.extend(result.iter().cloned());
2450 if signature.is_some() {
2451 *sig = signature.clone();
2452 }
2453 }
2454 }
2455 StepDelta::Unknown { delta_type, .. } => {
2456 tracing::debug!(
2457 "Skipping unknown StepDelta type '{}' during accumulation",
2458 delta_type
2459 );
2460 }
2461 }
2462 }
2463
2464 pub(crate) fn stop(&mut self, index: usize) {
2469 if let Some(entry) = self.steps.get_mut(&index) {
2470 Self::finalize_entry(entry);
2471 }
2472 }
2473
2474 pub(crate) fn record_cumulative_usage(&mut self, usage: crate::response::UsageMetadata) {
2477 self.last_cumulative_usage = Some(usage);
2478 }
2479
2480 pub(crate) fn take_cumulative_usage(&mut self) -> Option<crate::response::UsageMetadata> {
2482 self.last_cumulative_usage.take()
2483 }
2484
2485 fn finalize_entry(entry: &mut AccumulatedStep) {
2486 if entry.args_buffer.is_empty() {
2487 return;
2488 }
2489 if let Step::FunctionCall { arguments, .. } = &mut entry.step {
2490 match serde_json::from_str::<serde_json::Value>(&entry.args_buffer) {
2491 Ok(parsed) => *arguments = parsed,
2492 Err(e) => {
2493 tracing::warn!(
2494 "Failed to parse accumulated arguments_delta buffer as JSON: {}. \
2495 Preserving raw string.",
2496 e
2497 );
2498 *arguments = serde_json::Value::String(std::mem::take(&mut entry.args_buffer));
2499 }
2500 }
2501 }
2502 entry.args_buffer.clear();
2503 }
2504
2505 pub(crate) fn finish(mut self) -> Vec<Step> {
2507 for entry in self.steps.values_mut() {
2508 Self::finalize_entry(entry);
2509 }
2510 self.steps.into_values().map(|e| e.step).collect()
2511 }
2512}
2513
2514#[cfg(test)]
2515mod tests {
2516 use super::*;
2517 use serde_json::json;
2518
2519 #[test]
2525 fn test_step_user_input_roundtrip() {
2526 let json_str = r#"{"type":"user_input","content":[{"type":"text","text":"Hello"}]}"#;
2527 let step: Step = serde_json::from_str(json_str).unwrap();
2528 assert!(matches!(&step, Step::UserInput { content } if content.len() == 1));
2529 assert_eq!(step.as_text(), Some("Hello"));
2530
2531 let out = serde_json::to_value(&step).unwrap();
2532 assert_eq!(out["type"], "user_input");
2533 assert_eq!(out["content"][0]["text"], "Hello");
2534 }
2535
2536 #[test]
2537 fn test_step_model_output_with_error() {
2538 let json_str = r#"{
2539 "type": "model_output",
2540 "content": [{"type": "text", "text": "Partial"}],
2541 "error": {"code": 8, "message": "quota exhausted"}
2542 }"#;
2543 let step: Step = serde_json::from_str(json_str).unwrap();
2544 match &step {
2545 Step::ModelOutput { content, error } => {
2546 assert_eq!(content.len(), 1);
2547 let error = error.as_ref().unwrap();
2548 assert_eq!(error.code, Some(8));
2549 assert_eq!(error.message.as_deref(), Some("quota exhausted"));
2550 }
2551 other => panic!("Expected ModelOutput, got {other:?}"),
2552 }
2553 }
2554
2555 #[test]
2556 fn test_step_thought_roundtrip() {
2557 let json_str = r#"{
2558 "type": "thought",
2559 "signature": "sig-abc",
2560 "summary": [{"type": "text", "text": "Thinking about it"}]
2561 }"#;
2562 let step: Step = serde_json::from_str(json_str).unwrap();
2563 match &step {
2564 Step::Thought { signature, summary } => {
2565 assert_eq!(signature.as_deref(), Some("sig-abc"));
2566 assert_eq!(summary.len(), 1);
2567 }
2568 other => panic!("Expected Thought, got {other:?}"),
2569 }
2570 assert_eq!(step.signature(), Some("sig-abc"));
2571
2572 let out = serde_json::to_value(&step).unwrap();
2573 assert_eq!(out["type"], "thought");
2574 assert_eq!(out["signature"], "sig-abc");
2575 }
2576
2577 #[test]
2578 fn test_step_function_call_roundtrip() {
2579 let json_str = r#"{
2580 "type": "function_call",
2581 "id": "call_1",
2582 "name": "get_weather",
2583 "arguments": {"city": "Tokyo"}
2584 }"#;
2585 let step: Step = serde_json::from_str(json_str).unwrap();
2586 match &step {
2587 Step::FunctionCall {
2588 id,
2589 name,
2590 arguments,
2591 signature,
2592 } => {
2593 assert_eq!(id, "call_1");
2594 assert_eq!(name, "get_weather");
2595 assert_eq!(arguments["city"], "Tokyo");
2596 assert_eq!(*signature, None);
2597 }
2598 other => panic!("Expected FunctionCall, got {other:?}"),
2599 }
2600
2601 let out = serde_json::to_value(&step).unwrap();
2602 assert_eq!(out["type"], "function_call");
2603 assert_eq!(out["id"], "call_1");
2604 assert_eq!(out["arguments"]["city"], "Tokyo");
2605 assert!(out.get("signature").is_none());
2607 }
2608
2609 #[test]
2610 fn test_step_function_call_roundtrip_preserves_signature() {
2611 let json_str = r#"{
2614 "type": "function_call",
2615 "id": "call_2",
2616 "name": "get_weather",
2617 "arguments": {"city": "Paris"},
2618 "signature": "sig-fc-123"
2619 }"#;
2620 let step: Step = serde_json::from_str(json_str).unwrap();
2621 match &step {
2622 Step::FunctionCall { signature, .. } => {
2623 assert_eq!(signature.as_deref(), Some("sig-fc-123"));
2624 }
2625 other => panic!("Expected FunctionCall, got {other:?}"),
2626 }
2627 assert_eq!(step.signature(), Some("sig-fc-123"));
2628
2629 let out = serde_json::to_value(&step).unwrap();
2630 assert_eq!(out["type"], "function_call");
2631 assert_eq!(out["signature"], "sig-fc-123");
2632 }
2633
2634 #[test]
2635 fn test_step_function_result_roundtrip_preserves_signature() {
2636 let json_str = r#"{
2637 "type": "function_result",
2638 "call_id": "call_2",
2639 "name": "get_weather",
2640 "result": "sunny",
2641 "signature": "sig-fr-456"
2642 }"#;
2643 let step: Step = serde_json::from_str(json_str).unwrap();
2644 match &step {
2645 Step::FunctionResult { signature, .. } => {
2646 assert_eq!(signature.as_deref(), Some("sig-fr-456"));
2647 }
2648 other => panic!("Expected FunctionResult, got {other:?}"),
2649 }
2650 assert_eq!(step.signature(), Some("sig-fr-456"));
2651
2652 let out = serde_json::to_value(&step).unwrap();
2653 assert_eq!(out["type"], "function_result");
2654 assert_eq!(out["signature"], "sig-fr-456");
2655
2656 let constructed = Step::function_result("get_weather", "call_2", "sunny");
2658 let out = serde_json::to_value(&constructed).unwrap();
2659 assert!(out.get("signature").is_none());
2660 }
2661
2662 #[test]
2663 fn test_step_function_result_payload_union() {
2664 let s: Step = serde_json::from_str(
2666 r#"{"type":"function_result","call_id":"c1","result":"22 degrees"}"#,
2667 )
2668 .unwrap();
2669 match &s {
2670 Step::FunctionResult { result, .. } => {
2671 assert_eq!(result.as_text(), Some("22 degrees"));
2672 }
2673 other => panic!("Expected FunctionResult, got {other:?}"),
2674 }
2675
2676 let s: Step = serde_json::from_str(
2678 r#"{"type":"function_result","call_id":"c2","result":{"temp":22},"is_error":false}"#,
2679 )
2680 .unwrap();
2681 match &s {
2682 Step::FunctionResult {
2683 result, is_error, ..
2684 } => {
2685 assert_eq!(result.as_json().unwrap()["temp"], 22);
2686 assert_eq!(*is_error, Some(false));
2687 }
2688 other => panic!("Expected FunctionResult, got {other:?}"),
2689 }
2690
2691 let s: Step = serde_json::from_str(
2693 r#"{"type":"function_result","call_id":"c3","result":[{"type":"text","text":"hi"}]}"#,
2694 )
2695 .unwrap();
2696 match &s {
2697 Step::FunctionResult { result, .. } => {
2698 let contents = result.as_contents().unwrap();
2699 assert_eq!(contents.len(), 1);
2700 assert_eq!(contents[0].as_text(), Some("hi"));
2701 }
2702 other => panic!("Expected FunctionResult, got {other:?}"),
2703 }
2704 }
2705
2706 #[test]
2707 fn test_step_code_execution_nested_arguments_roundtrip() {
2708 let json_str = r#"{
2709 "type": "code_execution_call",
2710 "id": "exec_1",
2711 "arguments": {"language": "python", "code": "print(42)"},
2712 "signature": "sig-1"
2713 }"#;
2714 let step: Step = serde_json::from_str(json_str).unwrap();
2715 match &step {
2716 Step::CodeExecutionCall {
2717 id,
2718 language,
2719 code,
2720 signature,
2721 } => {
2722 assert_eq!(id, "exec_1");
2723 assert_eq!(*language, CodeExecutionLanguage::Python);
2724 assert_eq!(code, "print(42)");
2725 assert_eq!(signature.as_deref(), Some("sig-1"));
2726 }
2727 other => panic!("Expected CodeExecutionCall, got {other:?}"),
2728 }
2729
2730 let out = serde_json::to_value(&step).unwrap();
2732 assert_eq!(out["arguments"]["language"], "python");
2733 assert_eq!(out["arguments"]["code"], "print(42)");
2734 assert_eq!(out["signature"], "sig-1");
2735 }
2736
2737 #[test]
2738 fn test_step_google_search_call_with_search_type() {
2739 let json_str = r#"{
2740 "type": "google_search_call",
2741 "id": "s1",
2742 "arguments": {"queries": ["rust serde"]},
2743 "search_type": "web_search",
2744 "signature": "sig"
2745 }"#;
2746 let step: Step = serde_json::from_str(json_str).unwrap();
2747 match &step {
2748 Step::GoogleSearchCall {
2749 queries,
2750 search_type,
2751 ..
2752 } => {
2753 assert_eq!(queries, &["rust serde".to_string()]);
2754 assert!(matches!(
2755 search_type,
2756 Some(crate::tools::SearchType::WebSearch)
2757 ));
2758 }
2759 other => panic!("Expected GoogleSearchCall, got {other:?}"),
2760 }
2761 }
2762
2763 #[test]
2764 fn test_step_mcp_server_tool_call_roundtrip() {
2765 let json_str = r#"{
2766 "type": "mcp_server_tool_call",
2767 "id": "m1",
2768 "name": "read_file",
2769 "server_name": "fs",
2770 "arguments": {"path": "/tmp/x"}
2771 }"#;
2772 let step: Step = serde_json::from_str(json_str).unwrap();
2773 match &step {
2774 Step::McpServerToolCall {
2775 name, server_name, ..
2776 } => {
2777 assert_eq!(name, "read_file");
2778 assert_eq!(server_name, "fs");
2779 }
2780 other => panic!("Expected McpServerToolCall, got {other:?}"),
2781 }
2782 let out = serde_json::to_value(&step).unwrap();
2783 assert_eq!(out["server_name"], "fs");
2784 }
2785
2786 #[test]
2787 #[cfg(not(feature = "strict-unknown"))]
2788 fn test_step_unknown_preserves_data_and_roundtrips() {
2789 let json_str = r#"{"type":"quantum_step","novel_field":42}"#;
2790 let step: Step = serde_json::from_str(json_str).unwrap();
2791 assert!(step.is_unknown());
2792 assert_eq!(step.unknown_step_type(), Some("quantum_step"));
2793 assert_eq!(step.unknown_data().unwrap()["novel_field"], 42);
2794
2795 let out = serde_json::to_value(&step).unwrap();
2796 assert_eq!(out["type"], "quantum_step");
2797 assert_eq!(out["novel_field"], 42);
2798 }
2799
2800 #[test]
2801 fn test_step_type_accessor() {
2802 assert_eq!(Step::user_text("x").step_type(), "user_input");
2803 assert_eq!(
2804 Step::function_call("id", "f", json!({})).step_type(),
2805 "function_call"
2806 );
2807 assert_eq!(
2808 Step::Unknown {
2809 step_type: "future".into(),
2810 data: serde_json::Value::Null
2811 }
2812 .step_type(),
2813 "future"
2814 );
2815 }
2816
2817 #[test]
2822 fn test_step_delta_text() {
2823 let delta: StepDelta = serde_json::from_str(r#"{"type":"text","text":"Hel"}"#).unwrap();
2824 assert_eq!(delta.as_text(), Some("Hel"));
2825 let out = serde_json::to_value(&delta).unwrap();
2826 assert_eq!(out, json!({"type": "text", "text": "Hel"}));
2827 }
2828
2829 #[test]
2830 fn test_step_delta_arguments_delta() {
2831 let delta: StepDelta =
2832 serde_json::from_str(r#"{"type":"arguments_delta","arguments":"{\"city\": \"To"}"#)
2833 .unwrap();
2834 assert_eq!(delta.as_arguments_delta(), Some("{\"city\": \"To"));
2835 }
2836
2837 #[test]
2838 fn test_step_delta_audio_with_rate_and_channels() {
2839 let delta: StepDelta = serde_json::from_str(
2840 r#"{"type":"audio","data":"QUJD","mime_type":"audio/l16","rate":24000,"sample_rate":24000,"channels":1}"#,
2841 )
2842 .unwrap();
2843 match &delta {
2844 StepDelta::Audio {
2845 sample_rate,
2846 channels,
2847 rate,
2848 ..
2849 } => {
2850 assert_eq!(*sample_rate, Some(24000));
2851 assert_eq!(*rate, Some(24000));
2852 assert_eq!(*channels, Some(1));
2853 }
2854 other => panic!("Expected Audio, got {other:?}"),
2855 }
2856 }
2857
2858 #[test]
2859 fn test_step_delta_thought_summary_and_signature() {
2860 let summary: StepDelta = serde_json::from_str(
2861 r#"{"type":"thought_summary","content":{"type":"text","text":"Analyzing"}}"#,
2862 )
2863 .unwrap();
2864 assert!(matches!(summary, StepDelta::ThoughtSummary { .. }));
2865
2866 let sig: StepDelta =
2867 serde_json::from_str(r#"{"type":"thought_signature","signature":"abc123"}"#).unwrap();
2868 assert!(matches!(
2869 sig,
2870 StepDelta::ThoughtSignature { signature: Some(s) } if s == "abc123"
2871 ));
2872 }
2873
2874 #[test]
2875 fn test_step_delta_text_annotation() {
2876 let delta: StepDelta = serde_json::from_str(
2877 r#"{"type":"text_annotation_delta","annotations":[
2878 {"type":"url_citation","url":"https://example.com","title":"Example","start_index":0,"end_index":5}
2879 ]}"#,
2880 )
2881 .unwrap();
2882 match &delta {
2883 StepDelta::TextAnnotation { annotations } => {
2884 assert_eq!(annotations.len(), 1);
2885 assert!(matches!(annotations[0], Annotation::UrlCitation { .. }));
2886 }
2887 other => panic!("Expected TextAnnotation, got {other:?}"),
2888 }
2889 }
2890
2891 #[test]
2892 fn test_step_delta_unknown_roundtrip() {
2893 let delta: StepDelta = serde_json::from_str(r#"{"type":"hologram","frames":3}"#).unwrap();
2894 assert!(delta.is_unknown());
2895 assert_eq!(delta.unknown_delta_type(), Some("hologram"));
2896 assert_eq!(delta.unknown_data().unwrap()["frames"], 3);
2897 let out = serde_json::to_value(&delta).unwrap();
2898 assert_eq!(out["type"], "hologram");
2899 assert_eq!(out["frames"], 3);
2900 }
2901
2902 #[test]
2907 fn test_function_result_payload_from_value() {
2908 assert!(matches!(
2909 FunctionResultPayload::from_value(json!("text")),
2910 FunctionResultPayload::Text(_)
2911 ));
2912 assert!(matches!(
2913 FunctionResultPayload::from_value(json!({"a": 1})),
2914 FunctionResultPayload::Json(_)
2915 ));
2916 assert!(matches!(
2917 FunctionResultPayload::from_value(json!([{"type": "text", "text": "hi"}])),
2918 FunctionResultPayload::Contents(_)
2919 ));
2920 assert!(matches!(
2922 FunctionResultPayload::from_value(json!([1, 2, 3])),
2923 FunctionResultPayload::Json(_)
2924 ));
2925 }
2926
2927 #[test]
2928 fn test_function_result_payload_roundtrip() {
2929 for payload in [
2930 FunctionResultPayload::Text("hello".into()),
2931 FunctionResultPayload::Json(json!({"k": [1, 2]})),
2932 FunctionResultPayload::Contents(vec![Content::text("block")]),
2933 ] {
2934 let serialized = serde_json::to_string(&payload).unwrap();
2935 let back: FunctionResultPayload = serde_json::from_str(&serialized).unwrap();
2936 assert_eq!(payload, back);
2937 }
2938 }
2939
2940 #[test]
2945 fn test_accumulator_text_stream() {
2946 let mut acc = StepAccumulator::new();
2947 acc.start(0, Step::model_output(vec![]));
2948 acc.apply_delta(
2949 0,
2950 &StepDelta::Text {
2951 text: "Hello ".into(),
2952 },
2953 );
2954 acc.apply_delta(
2955 0,
2956 &StepDelta::Text {
2957 text: "world".into(),
2958 },
2959 );
2960 acc.stop(0);
2961 let steps = acc.finish();
2962 assert_eq!(steps.len(), 1);
2963 assert_eq!(steps[0].as_text(), Some("Hello world"));
2964 }
2965
2966 #[test]
2967 fn test_accumulator_image_deltas_accumulate_into_one_block() {
2968 let mut acc = StepAccumulator::new();
2969 acc.start(0, Step::model_output(vec![]));
2970 acc.apply_delta(
2971 0,
2972 &StepDelta::Image {
2973 data: Some("AAAA".into()),
2974 uri: None,
2975 mime_type: Some("image/png".into()),
2976 resolution: None,
2977 },
2978 );
2979 acc.apply_delta(
2980 0,
2981 &StepDelta::Image {
2982 data: Some("BBBB".into()),
2983 uri: None,
2984 mime_type: Some("image/png".into()),
2985 resolution: None,
2986 },
2987 );
2988 acc.stop(0);
2989 let steps = acc.finish();
2990 match &steps[0] {
2991 Step::ModelOutput { content, .. } => {
2992 assert_eq!(content.len(), 1);
2993 match &content[0] {
2994 Content::Image { data, .. } => assert_eq!(data.as_deref(), Some("AAAABBBB")),
2995 other => panic!("Expected Image, got {other:?}"),
2996 }
2997 }
2998 other => panic!("Expected ModelOutput, got {other:?}"),
2999 }
3000 }
3001
3002 #[test]
3003 fn test_accumulator_video_and_document_deltas_accumulate() {
3004 let mut acc = StepAccumulator::new();
3005 acc.start(0, Step::model_output(vec![]));
3006 acc.apply_delta(
3007 0,
3008 &StepDelta::Video {
3009 data: Some("VV".into()),
3010 uri: None,
3011 mime_type: Some("video/mp4".into()),
3012 resolution: None,
3013 },
3014 );
3015 acc.apply_delta(
3016 0,
3017 &StepDelta::Video {
3018 data: Some("WW".into()),
3019 uri: None,
3020 mime_type: Some("video/mp4".into()),
3021 resolution: None,
3022 },
3023 );
3024 acc.apply_delta(
3025 0,
3026 &StepDelta::Document {
3027 data: Some("DD".into()),
3028 uri: None,
3029 mime_type: Some("application/pdf".into()),
3030 },
3031 );
3032 acc.apply_delta(
3033 0,
3034 &StepDelta::Document {
3035 data: Some("EE".into()),
3036 uri: None,
3037 mime_type: Some("application/pdf".into()),
3038 },
3039 );
3040 acc.stop(0);
3041 let steps = acc.finish();
3042 match &steps[0] {
3043 Step::ModelOutput { content, .. } => {
3044 assert_eq!(content.len(), 2);
3045 match &content[0] {
3046 Content::Video { data, .. } => assert_eq!(data.as_deref(), Some("VVWW")),
3047 other => panic!("Expected Video, got {other:?}"),
3048 }
3049 match &content[1] {
3050 Content::Document { data, .. } => assert_eq!(data.as_deref(), Some("DDEE")),
3051 other => panic!("Expected Document, got {other:?}"),
3052 }
3053 }
3054 other => panic!("Expected ModelOutput, got {other:?}"),
3055 }
3056 }
3057
3058 #[test]
3059 fn test_accumulator_image_uri_delta_pushes_new_block() {
3060 let mut acc = StepAccumulator::new();
3063 acc.start(0, Step::model_output(vec![]));
3064 acc.apply_delta(
3065 0,
3066 &StepDelta::Image {
3067 data: Some("AAAA".into()),
3068 uri: None,
3069 mime_type: Some("image/png".into()),
3070 resolution: None,
3071 },
3072 );
3073 acc.apply_delta(
3074 0,
3075 &StepDelta::Image {
3076 data: None,
3077 uri: Some("https://example.com/img.png".into()),
3078 mime_type: Some("image/png".into()),
3079 resolution: None,
3080 },
3081 );
3082 acc.stop(0);
3083 let steps = acc.finish();
3084 match &steps[0] {
3085 Step::ModelOutput { content, .. } => assert_eq!(content.len(), 2),
3086 other => panic!("Expected ModelOutput, got {other:?}"),
3087 }
3088 }
3089
3090 #[test]
3091 fn test_accumulator_function_call_arguments_delta() {
3092 let mut acc = StepAccumulator::new();
3093 acc.start(
3094 0,
3095 Step::FunctionCall {
3096 id: "c1".into(),
3097 name: "get_weather".into(),
3098 arguments: serde_json::Value::Null,
3099 signature: None,
3100 },
3101 );
3102 acc.apply_delta(
3103 0,
3104 &StepDelta::ArgumentsDelta {
3105 arguments: "{\"city\": ".into(),
3106 },
3107 );
3108 acc.apply_delta(
3109 0,
3110 &StepDelta::ArgumentsDelta {
3111 arguments: "\"Tokyo\"}".into(),
3112 },
3113 );
3114 acc.stop(0);
3115 let steps = acc.finish();
3116 match &steps[0] {
3117 Step::FunctionCall { arguments, .. } => assert_eq!(arguments["city"], "Tokyo"),
3118 other => panic!("Expected FunctionCall, got {other:?}"),
3119 }
3120 }
3121
3122 #[test]
3123 fn test_accumulator_thought_summary_and_signature() {
3124 let mut acc = StepAccumulator::new();
3125 acc.start(
3126 0,
3127 Step::Thought {
3128 signature: None,
3129 summary: vec![],
3130 },
3131 );
3132 acc.apply_delta(
3133 0,
3134 &StepDelta::ThoughtSummary {
3135 content: Some(Content::text("Consider ")),
3136 },
3137 );
3138 acc.apply_delta(
3139 0,
3140 &StepDelta::ThoughtSummary {
3141 content: Some(Content::text("the problem")),
3142 },
3143 );
3144 acc.apply_delta(
3145 0,
3146 &StepDelta::ThoughtSignature {
3147 signature: Some("sig-xyz".into()),
3148 },
3149 );
3150 let steps = acc.finish();
3151 match &steps[0] {
3152 Step::Thought { signature, summary } => {
3153 assert_eq!(signature.as_deref(), Some("sig-xyz"));
3154 assert_eq!(summary[0].as_text(), Some("Consider the problem"));
3155 }
3156 other => panic!("Expected Thought, got {other:?}"),
3157 }
3158 }
3159
3160 #[test]
3161 fn test_accumulator_delta_without_start_creates_model_output() {
3162 let mut acc = StepAccumulator::new();
3163 acc.apply_delta(
3164 0,
3165 &StepDelta::Text {
3166 text: "orphan".into(),
3167 },
3168 );
3169 let steps = acc.finish();
3170 assert_eq!(steps[0].as_text(), Some("orphan"));
3171 }
3172
3173 #[test]
3174 fn test_accumulator_orders_steps_by_index() {
3175 let mut acc = StepAccumulator::new();
3176 acc.start(2, Step::model_text("second"));
3177 acc.start(1, Step::thought("sig"));
3178 let steps = acc.finish();
3179 assert!(matches!(steps[0], Step::Thought { .. }));
3180 assert!(matches!(steps[1], Step::ModelOutput { .. }));
3181 }
3182}