1use std::collections::HashMap;
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use serde_with::skip_serializing_none;
9use validator::{Validate, ValidationError};
10
11use super::common::{default_model, default_true, Function, GenerationRequest};
12
13#[skip_serializing_none]
18#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
19#[validate(schema(function = "validate_interactions_request"))]
20pub struct InteractionsRequest {
21 pub model: Option<String>,
24
25 pub agent: Option<String>,
28
29 #[validate(custom(function = "validate_input"))]
31 pub input: InteractionsInput,
32
33 pub system_instruction: Option<String>,
35
36 #[validate(custom(function = "validate_tools"))]
38 pub tools: Option<Vec<InteractionsTool>>,
39
40 pub response_format: Option<Value>,
42
43 pub response_mime_type: Option<String>,
45
46 #[serde(default)]
48 pub stream: bool,
49
50 #[serde(default = "default_true")]
52 pub store: bool,
53
54 #[serde(default)]
56 pub background: bool,
57
58 pub generation_config: Option<GenerationConfig>,
60
61 pub agent_config: Option<AgentConfig>,
63
64 pub response_modalities: Option<Vec<ResponseModality>>,
66
67 pub previous_interaction_id: Option<String>,
69}
70
71impl Default for InteractionsRequest {
72 fn default() -> Self {
73 Self {
74 model: Some(default_model()),
75 agent: None,
76 agent_config: None,
77 input: InteractionsInput::Text(String::new()),
78 system_instruction: None,
79 previous_interaction_id: None,
80 tools: None,
81 generation_config: None,
82 response_format: None,
83 response_mime_type: None,
84 response_modalities: None,
85 stream: false,
86 background: false,
87 store: true,
88 }
89 }
90}
91
92impl GenerationRequest for InteractionsRequest {
93 fn is_stream(&self) -> bool {
94 self.stream
95 }
96
97 fn get_model(&self) -> Option<&str> {
98 self.model.as_deref()
99 }
100
101 fn extract_text_for_routing(&self) -> String {
102 fn extract_from_content(content: &Content) -> Option<String> {
103 match content {
104 Content::Text { text, .. } => text.clone(),
105 _ => None,
106 }
107 }
108
109 fn extract_from_turn(turn: &Turn) -> String {
110 match &turn.content {
111 Some(TurnContent::Text(text)) => text.clone(),
112 Some(TurnContent::Contents(contents)) => contents
113 .iter()
114 .filter_map(extract_from_content)
115 .collect::<Vec<String>>()
116 .join(" "),
117 None => String::new(),
118 }
119 }
120
121 match &self.input {
122 InteractionsInput::Text(text) => text.clone(),
123 InteractionsInput::Content(content) => {
124 extract_from_content(content).unwrap_or_default()
125 }
126 InteractionsInput::Contents(contents) => contents
127 .iter()
128 .filter_map(extract_from_content)
129 .collect::<Vec<String>>()
130 .join(" "),
131 InteractionsInput::Turns(turns) => turns
132 .iter()
133 .map(extract_from_turn)
134 .collect::<Vec<String>>()
135 .join(" "),
136 }
137 }
138}
139
140#[skip_serializing_none]
145#[derive(Debug, Clone, Default, Deserialize, Serialize)]
146pub struct Interaction {
147 pub object: Option<String>,
149
150 pub model: Option<String>,
152
153 pub agent: Option<String>,
155
156 pub id: String,
158
159 pub status: InteractionsStatus,
161
162 pub created: Option<String>,
164
165 pub updated: Option<String>,
167
168 pub role: Option<String>,
170
171 pub outputs: Option<Vec<Content>>,
173
174 pub usage: Option<InteractionsUsage>,
176
177 pub previous_interaction_id: Option<String>,
179}
180
181impl Interaction {
182 pub fn is_complete(&self) -> bool {
184 matches!(self.status, InteractionsStatus::Completed)
185 }
186
187 pub fn is_in_progress(&self) -> bool {
189 matches!(self.status, InteractionsStatus::InProgress)
190 }
191
192 pub fn is_failed(&self) -> bool {
194 matches!(self.status, InteractionsStatus::Failed)
195 }
196
197 pub fn requires_action(&self) -> bool {
199 matches!(self.status, InteractionsStatus::RequiresAction)
200 }
201}
202
203#[skip_serializing_none]
210#[derive(Debug, Clone, Deserialize, Serialize)]
211#[serde(tag = "event_type")]
212pub enum InteractionStreamEvent {
213 #[serde(rename = "interaction.start")]
215 InteractionStart {
216 interaction: Option<Interaction>,
218 event_id: Option<String>,
220 },
221
222 #[serde(rename = "interaction.complete")]
224 InteractionComplete {
225 interaction: Option<Interaction>,
227 event_id: Option<String>,
229 },
230
231 #[serde(rename = "interaction.status_update")]
233 InteractionStatusUpdate {
234 interaction_id: Option<String>,
236 status: Option<InteractionsStatus>,
238 event_id: Option<String>,
240 },
241
242 #[serde(rename = "content.start")]
244 ContentStart {
245 index: Option<u32>,
247 content: Option<Content>,
249 event_id: Option<String>,
251 },
252
253 #[serde(rename = "content.delta")]
255 ContentDelta {
256 index: Option<u32>,
258 event_id: Option<String>,
260 delta: Option<Delta>,
262 },
263
264 #[serde(rename = "content.stop")]
266 ContentStop {
267 index: Option<u32>,
269 event_id: Option<String>,
271 },
272
273 #[serde(rename = "error")]
275 Error {
276 error: Option<InteractionsError>,
278 event_id: Option<String>,
280 },
281}
282
283#[skip_serializing_none]
286#[derive(Debug, Clone, Deserialize, Serialize)]
287#[serde(tag = "type", rename_all = "snake_case")]
288pub enum Delta {
289 Text {
291 text: Option<String>,
292 annotations: Option<Vec<Annotation>>,
293 },
294 Image {
296 data: Option<String>,
297 uri: Option<String>,
298 mime_type: Option<ImageMimeType>,
299 resolution: Option<MediaResolution>,
300 },
301 Audio {
303 data: Option<String>,
304 uri: Option<String>,
305 mime_type: Option<AudioMimeType>,
306 },
307 Document {
309 data: Option<String>,
310 uri: Option<String>,
311 mime_type: Option<DocumentMimeType>,
312 },
313 Video {
315 data: Option<String>,
316 uri: Option<String>,
317 mime_type: Option<VideoMimeType>,
318 resolution: Option<MediaResolution>,
319 },
320 ThoughtSummary {
322 content: Option<ThoughtSummaryContent>,
323 },
324 ThoughtSignature { signature: Option<String> },
326 FunctionCall {
328 name: Option<String>,
329 arguments: Option<String>,
330 id: Option<String>,
331 },
332 FunctionResult {
334 name: Option<String>,
335 is_error: Option<bool>,
336 result: Option<Value>,
337 call_id: Option<String>,
338 },
339 CodeExecutionCall {
341 arguments: Option<CodeExecutionArguments>,
342 id: Option<String>,
343 },
344 CodeExecutionResult {
346 result: Option<String>,
347 is_error: Option<bool>,
348 signature: Option<String>,
349 call_id: Option<String>,
350 },
351 UrlContextCall {
353 arguments: Option<UrlContextArguments>,
354 id: Option<String>,
355 },
356 UrlContextResult {
358 signature: Option<String>,
359 result: Option<Vec<UrlContextResultData>>,
360 is_error: Option<bool>,
361 call_id: Option<String>,
362 },
363 GoogleSearchCall {
365 arguments: Option<GoogleSearchArguments>,
366 id: Option<String>,
367 },
368 GoogleSearchResult {
370 signature: Option<String>,
371 result: Option<Vec<GoogleSearchResultData>>,
372 is_error: Option<bool>,
373 call_id: Option<String>,
374 },
375 FileSearchCall { id: Option<String> },
377 FileSearchResult {
379 result: Option<Vec<FileSearchResultData>>,
380 },
381 McpServerToolCall {
383 name: Option<String>,
384 server_name: Option<String>,
385 arguments: Option<Value>,
386 id: Option<String>,
387 },
388 McpServerToolResult {
390 name: Option<String>,
391 server_name: Option<String>,
392 result: Option<Value>,
393 call_id: Option<String>,
394 },
395}
396
397#[skip_serializing_none]
399#[derive(Debug, Clone, Deserialize, Serialize)]
400pub struct InteractionsError {
401 pub code: Option<String>,
403 pub message: Option<String>,
405}
406
407#[skip_serializing_none]
413#[derive(Debug, Clone, Default, Deserialize, Serialize)]
414pub struct InteractionsGetParams {
415 pub stream: Option<bool>,
417 pub last_event_id: Option<String>,
419 pub api_version: Option<String>,
421}
422
423#[skip_serializing_none]
425#[derive(Debug, Clone, Default, Deserialize, Serialize)]
426pub struct InteractionsDeleteParams {
427 pub api_version: Option<String>,
429}
430
431#[skip_serializing_none]
433#[derive(Debug, Clone, Default, Deserialize, Serialize)]
434pub struct InteractionsCancelParams {
435 pub api_version: Option<String>,
437}
438
439#[skip_serializing_none]
446#[derive(Debug, Clone, Deserialize, Serialize)]
447#[serde(tag = "type", rename_all = "snake_case")]
448pub enum InteractionsTool {
449 Function(Function),
451 GoogleSearch {},
453 CodeExecution {},
455 UrlContext {},
457 McpServer {
459 name: Option<String>,
460 url: Option<String>,
461 headers: Option<HashMap<String, String>>,
462 allowed_tools: Option<AllowedTools>,
463 },
464 FileSearch {
466 file_search_store_names: Option<Vec<String>>,
468 top_k: Option<u32>,
470 metadata_filter: Option<String>,
472 },
473}
474
475#[skip_serializing_none]
477#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
478pub struct AllowedTools {
479 pub mode: Option<ToolChoiceType>,
481 pub tools: Option<Vec<String>>,
483}
484
485#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
486#[serde(rename_all = "snake_case")]
487pub enum ToolChoiceType {
488 Auto,
489 Any,
490 None,
491 Validated,
492}
493
494#[skip_serializing_none]
499#[derive(Debug, Clone, Deserialize, Serialize)]
500pub struct GenerationConfig {
501 pub temperature: Option<f32>,
502
503 pub top_p: Option<f32>,
504
505 pub seed: Option<i64>,
506
507 pub stop_sequences: Option<Vec<String>>,
508
509 pub tool_choice: Option<ToolChoice>,
510
511 pub thinking_level: Option<ThinkingLevel>,
512
513 pub thinking_summaries: Option<ThinkingSummaries>,
514
515 pub max_output_tokens: Option<u32>,
516
517 pub speech_config: Option<Vec<SpeechConfig>>,
518
519 pub image_config: Option<ImageConfig>,
520}
521
522#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
523#[serde(rename_all = "snake_case")]
524pub enum ThinkingLevel {
525 Minimal,
526 Low,
527 Medium,
528 High,
529}
530
531#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
532#[serde(rename_all = "snake_case")]
533pub enum ThinkingSummaries {
534 Auto,
535 None,
536}
537
538#[derive(Debug, Clone, Deserialize, Serialize)]
540#[serde(untagged)]
541pub enum ToolChoice {
542 Type(ToolChoiceType),
543 Config(ToolChoiceConfig),
544}
545
546#[skip_serializing_none]
547#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
548pub struct ToolChoiceConfig {
549 pub allowed_tools: Option<AllowedTools>,
550}
551
552#[skip_serializing_none]
553#[derive(Debug, Clone, Deserialize, Serialize)]
554pub struct SpeechConfig {
555 pub voice: Option<String>,
556 pub language: Option<String>,
557 pub speaker: Option<String>,
558}
559
560#[skip_serializing_none]
561#[derive(Debug, Clone, Deserialize, Serialize)]
562pub struct ImageConfig {
563 pub aspect_ratio: Option<AspectRatio>,
564 pub image_size: Option<ImageSize>,
565}
566
567#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
568pub enum AspectRatio {
569 #[serde(rename = "1:1")]
570 Square,
571 #[serde(rename = "2:3")]
572 Portrait2x3,
573 #[serde(rename = "3:2")]
574 Landscape3x2,
575 #[serde(rename = "3:4")]
576 Portrait3x4,
577 #[serde(rename = "4:3")]
578 Landscape4x3,
579 #[serde(rename = "4:5")]
580 Portrait4x5,
581 #[serde(rename = "5:4")]
582 Landscape5x4,
583 #[serde(rename = "9:16")]
584 Portrait9x16,
585 #[serde(rename = "16:9")]
586 Landscape16x9,
587 #[serde(rename = "21:9")]
588 UltraWide,
589}
590
591#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
592pub enum ImageSize {
593 #[serde(rename = "1K")]
594 OneK,
595 #[serde(rename = "2K")]
596 TwoK,
597 #[serde(rename = "4K")]
598 FourK,
599}
600
601#[derive(Debug, Clone, Deserialize, Serialize)]
604#[serde(tag = "type", rename_all = "snake_case")]
605pub enum AgentConfig {
606 Dynamic {},
608 #[serde(rename = "deep-research")]
610 DeepResearch {
611 #[serde(skip_serializing_if = "Option::is_none")]
613 thinking_summaries: Option<ThinkingSummaries>,
614 },
615}
616
617#[derive(Debug, Clone, Deserialize, Serialize)]
624#[serde(untagged)]
625pub enum InteractionsInput {
626 Text(String),
628 Content(Content),
630 Contents(Vec<Content>),
632 Turns(Vec<Turn>),
634}
635
636#[skip_serializing_none]
639#[derive(Debug, Clone, Deserialize, Serialize)]
640pub struct Turn {
641 pub role: Option<String>,
643 pub content: Option<TurnContent>,
645}
646
647#[derive(Debug, Clone, Deserialize, Serialize)]
649#[serde(untagged)]
650pub enum TurnContent {
651 Contents(Vec<Content>),
652 Text(String),
653}
654
655#[skip_serializing_none]
658#[derive(Debug, Clone, Deserialize, Serialize)]
659#[serde(tag = "type", rename_all = "snake_case")]
660pub enum Content {
661 Text {
663 text: Option<String>,
664 annotations: Option<Vec<Annotation>>,
665 },
666
667 Image {
669 data: Option<String>,
670 uri: Option<String>,
671 mime_type: Option<ImageMimeType>,
672 resolution: Option<MediaResolution>,
673 },
674
675 Audio {
677 data: Option<String>,
678 uri: Option<String>,
679 mime_type: Option<AudioMimeType>,
680 },
681
682 Document {
684 data: Option<String>,
685 uri: Option<String>,
686 mime_type: Option<DocumentMimeType>,
687 },
688
689 Video {
691 data: Option<String>,
692 uri: Option<String>,
693 mime_type: Option<VideoMimeType>,
694 resolution: Option<MediaResolution>,
695 },
696
697 Thought {
699 signature: Option<String>,
700 summary: Option<Vec<ThoughtSummaryContent>>,
701 },
702
703 FunctionCall {
705 name: String,
706 arguments: Value,
707 id: String,
708 },
709
710 FunctionResult {
712 name: Option<String>,
713 is_error: Option<bool>,
714 result: Value,
715 call_id: String,
716 },
717
718 CodeExecutionCall {
720 arguments: Option<CodeExecutionArguments>,
721 id: Option<String>,
722 },
723
724 CodeExecutionResult {
726 result: Option<String>,
727 is_error: Option<bool>,
728 signature: Option<String>,
729 call_id: Option<String>,
730 },
731
732 UrlContextCall {
734 arguments: Option<UrlContextArguments>,
735 id: Option<String>,
736 },
737
738 UrlContextResult {
740 signature: Option<String>,
741 result: Option<Vec<UrlContextResultData>>,
742 is_error: Option<bool>,
743 call_id: Option<String>,
744 },
745
746 GoogleSearchCall {
748 arguments: Option<GoogleSearchArguments>,
749 id: Option<String>,
750 },
751
752 GoogleSearchResult {
754 signature: Option<String>,
755 result: Option<Vec<GoogleSearchResultData>>,
756 is_error: Option<bool>,
757 call_id: Option<String>,
758 },
759
760 FileSearchCall { id: Option<String> },
762
763 FileSearchResult {
765 result: Option<Vec<FileSearchResultData>>,
766 },
767
768 McpServerToolCall {
770 name: String,
771 server_name: String,
772 arguments: Value,
773 id: String,
774 },
775
776 McpServerToolResult {
778 name: Option<String>,
779 server_name: Option<String>,
780 result: Value,
781 call_id: String,
782 },
783}
784
785#[skip_serializing_none]
787#[derive(Debug, Clone, Deserialize, Serialize)]
788#[serde(tag = "type", rename_all = "snake_case")]
789pub enum ThoughtSummaryContent {
790 Text {
792 text: Option<String>,
793 annotations: Option<Vec<Annotation>>,
794 },
795 Image {
797 data: Option<String>,
798 uri: Option<String>,
799 mime_type: Option<ImageMimeType>,
800 resolution: Option<MediaResolution>,
801 },
802}
803
804#[skip_serializing_none]
806#[derive(Debug, Clone, Deserialize, Serialize)]
807pub struct Annotation {
808 pub start_index: Option<u32>,
810 pub end_index: Option<u32>,
812 pub source: Option<String>,
814}
815
816#[skip_serializing_none]
818#[derive(Debug, Clone, Deserialize, Serialize)]
819pub struct UrlContextArguments {
820 pub urls: Option<Vec<String>>,
822}
823
824#[skip_serializing_none]
826#[derive(Debug, Clone, Deserialize, Serialize)]
827pub struct UrlContextResultData {
828 pub url: Option<String>,
830 pub status: Option<UrlContextStatus>,
832}
833
834#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
836#[serde(rename_all = "snake_case")]
837pub enum UrlContextStatus {
838 Success,
839 Error,
840 Paywall,
841 Unsafe,
842}
843
844#[skip_serializing_none]
846#[derive(Debug, Clone, Deserialize, Serialize)]
847pub struct GoogleSearchArguments {
848 pub queries: Option<Vec<String>>,
850}
851
852#[skip_serializing_none]
854#[derive(Debug, Clone, Deserialize, Serialize)]
855pub struct GoogleSearchResultData {
856 pub url: Option<String>,
858 pub title: Option<String>,
860 pub rendered_content: Option<String>,
862}
863
864#[skip_serializing_none]
866#[derive(Debug, Clone, Deserialize, Serialize)]
867pub struct FileSearchResultData {
868 pub title: Option<String>,
870 pub text: Option<String>,
872 pub file_search_store: Option<String>,
874}
875
876#[skip_serializing_none]
878#[derive(Debug, Clone, Deserialize, Serialize)]
879pub struct CodeExecutionArguments {
880 pub language: Option<CodeExecutionLanguage>,
882 pub code: Option<String>,
884}
885
886#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
888#[serde(rename_all = "snake_case")]
889pub enum CodeExecutionLanguage {
890 Python,
891}
892
893#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
895#[serde(rename_all = "snake_case")]
896pub enum MediaResolution {
897 Low,
898 Medium,
899 High,
900 UltraHigh,
901}
902
903#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
905pub enum ImageMimeType {
906 #[serde(rename = "image/png")]
907 Png,
908 #[serde(rename = "image/jpeg")]
909 Jpeg,
910 #[serde(rename = "image/webp")]
911 Webp,
912 #[serde(rename = "image/heic")]
913 Heic,
914 #[serde(rename = "image/heif")]
915 Heif,
916}
917
918impl ImageMimeType {
919 pub fn as_str(&self) -> &'static str {
920 match self {
921 ImageMimeType::Png => "image/png",
922 ImageMimeType::Jpeg => "image/jpeg",
923 ImageMimeType::Webp => "image/webp",
924 ImageMimeType::Heic => "image/heic",
925 ImageMimeType::Heif => "image/heif",
926 }
927 }
928}
929
930#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
932pub enum AudioMimeType {
933 #[serde(rename = "audio/wav")]
934 Wav,
935 #[serde(rename = "audio/mp3")]
936 Mp3,
937 #[serde(rename = "audio/aiff")]
938 Aiff,
939 #[serde(rename = "audio/aac")]
940 Aac,
941 #[serde(rename = "audio/ogg")]
942 Ogg,
943 #[serde(rename = "audio/flac")]
944 Flac,
945}
946
947impl AudioMimeType {
948 pub fn as_str(&self) -> &'static str {
949 match self {
950 AudioMimeType::Wav => "audio/wav",
951 AudioMimeType::Mp3 => "audio/mp3",
952 AudioMimeType::Aiff => "audio/aiff",
953 AudioMimeType::Aac => "audio/aac",
954 AudioMimeType::Ogg => "audio/ogg",
955 AudioMimeType::Flac => "audio/flac",
956 }
957 }
958}
959
960#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
962pub enum DocumentMimeType {
963 #[serde(rename = "application/pdf")]
964 Pdf,
965}
966
967impl DocumentMimeType {
968 pub fn as_str(&self) -> &'static str {
969 match self {
970 DocumentMimeType::Pdf => "application/pdf",
971 }
972 }
973}
974
975#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
977pub enum VideoMimeType {
978 #[serde(rename = "video/mp4")]
979 Mp4,
980 #[serde(rename = "video/mpeg")]
981 Mpeg,
982 #[serde(rename = "video/mov")]
983 Mov,
984 #[serde(rename = "video/avi")]
985 Avi,
986 #[serde(rename = "video/x-flv")]
987 Flv,
988 #[serde(rename = "video/mpg")]
989 Mpg,
990 #[serde(rename = "video/webm")]
991 Webm,
992 #[serde(rename = "video/wmv")]
993 Wmv,
994 #[serde(rename = "video/3gpp")]
995 ThreeGpp,
996}
997
998impl VideoMimeType {
999 pub fn as_str(&self) -> &'static str {
1000 match self {
1001 VideoMimeType::Mp4 => "video/mp4",
1002 VideoMimeType::Mpeg => "video/mpeg",
1003 VideoMimeType::Mov => "video/mov",
1004 VideoMimeType::Avi => "video/avi",
1005 VideoMimeType::Flv => "video/x-flv",
1006 VideoMimeType::Mpg => "video/mpg",
1007 VideoMimeType::Webm => "video/webm",
1008 VideoMimeType::Wmv => "video/wmv",
1009 VideoMimeType::ThreeGpp => "video/3gpp",
1010 }
1011 }
1012}
1013
1014#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
1019#[serde(rename_all = "snake_case")]
1020pub enum InteractionsStatus {
1021 #[default]
1022 InProgress,
1023 RequiresAction,
1024 Completed,
1025 Failed,
1026 Cancelled,
1027}
1028
1029#[skip_serializing_none]
1035#[derive(Debug, Clone, Deserialize, Serialize)]
1036pub struct ModalityTokens {
1037 pub modality: Option<ResponseModality>,
1038 pub tokens: Option<u32>,
1039}
1040
1041#[skip_serializing_none]
1042#[derive(Debug, Clone, Deserialize, Serialize)]
1043pub struct InteractionsUsage {
1044 pub total_input_tokens: Option<u32>,
1045 pub input_tokens_by_modality: Option<Vec<ModalityTokens>>,
1046 pub total_cached_tokens: Option<u32>,
1047 pub cached_tokens_by_modality: Option<Vec<ModalityTokens>>,
1048 pub total_output_tokens: Option<u32>,
1049 pub output_tokens_by_modality: Option<Vec<ModalityTokens>>,
1050 pub total_tool_use_tokens: Option<u32>,
1051 pub tool_use_tokens_by_modality: Option<Vec<ModalityTokens>>,
1052 pub total_thought_tokens: Option<u32>,
1053 pub total_tokens: Option<u32>,
1054}
1055
1056#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
1057#[serde(rename_all = "snake_case")]
1058pub enum ResponseModality {
1059 Text,
1060 Image,
1061 Audio,
1062}
1063
1064fn validate_interactions_request(req: &InteractionsRequest) -> Result<(), ValidationError> {
1065 let is_blank = |v: &Option<String>| v.as_deref().map(|s| s.trim().is_empty()).unwrap_or(true);
1066 if is_blank(&req.model) && is_blank(&req.agent) {
1068 return Err(ValidationError::new("model_or_agent_required"));
1069 }
1070 if req.response_format.is_some() && is_blank(&req.response_mime_type) {
1072 return Err(ValidationError::new("response_mime_type_required"));
1073 }
1074 Ok(())
1075}
1076
1077fn validate_tools(tools: &[InteractionsTool]) -> Result<(), ValidationError> {
1078 if tools
1080 .iter()
1081 .any(|t| matches!(t, InteractionsTool::FileSearch { .. }))
1082 {
1083 return Err(ValidationError::new("file_search_tool_not_supported"));
1084 }
1085 Ok(())
1086}
1087
1088fn validate_input(input: &InteractionsInput) -> Result<(), ValidationError> {
1089 fn has_file_search_content(content: &Content) -> bool {
1090 matches!(
1091 content,
1092 Content::FileSearchCall { .. } | Content::FileSearchResult { .. }
1093 )
1094 }
1095
1096 fn check_turn(turn: &Turn) -> bool {
1097 if let Some(content) = &turn.content {
1098 match content {
1099 TurnContent::Contents(contents) => contents.iter().any(has_file_search_content),
1100 TurnContent::Text(_) => false,
1101 }
1102 } else {
1103 false
1104 }
1105 }
1106
1107 let has_file_search = match input {
1108 InteractionsInput::Text(_) => false,
1109 InteractionsInput::Content(content) => has_file_search_content(content),
1110 InteractionsInput::Contents(contents) => contents.iter().any(has_file_search_content),
1111 InteractionsInput::Turns(turns) => turns.iter().any(check_turn),
1112 };
1113
1114 if has_file_search {
1115 return Err(ValidationError::new("file_search_content_not_supported"));
1116 }
1117 Ok(())
1118}