Skip to main content

gemini_rust/interactions/
model.rs

1use serde::{Deserialize, Serialize};
2
3// ============================================================================
4// Content Types (polymorphic, type-tagged)
5// ============================================================================
6
7/// Input content for interactions — type-tagged polymorphic.
8///
9/// Unlike the generateContent API's `Part` enum, Interactions API content
10/// uses a `type` discriminator field for each modality.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12#[serde(tag = "type", rename_all = "snake_case")]
13pub enum InteractionContent {
14    Text {
15        text: String,
16        #[serde(default, skip_serializing_if = "Vec::is_empty")]
17        annotations: Vec<Annotation>,
18    },
19    Image {
20        #[serde(skip_serializing_if = "Option::is_none")]
21        data: Option<String>,
22        #[serde(skip_serializing_if = "Option::is_none")]
23        uri: Option<String>,
24        #[serde(skip_serializing_if = "Option::is_none")]
25        mime_type: Option<ImageMimeType>,
26        #[serde(skip_serializing_if = "Option::is_none")]
27        resolution: Option<MediaResolution>,
28    },
29    Audio {
30        #[serde(skip_serializing_if = "Option::is_none")]
31        data: Option<String>,
32        #[serde(skip_serializing_if = "Option::is_none")]
33        uri: Option<String>,
34        #[serde(skip_serializing_if = "Option::is_none")]
35        mime_type: Option<AudioMimeType>,
36        #[serde(skip_serializing_if = "Option::is_none")]
37        channels: Option<i32>,
38        #[serde(skip_serializing_if = "Option::is_none")]
39        sample_rate: Option<i32>,
40    },
41    Document {
42        #[serde(skip_serializing_if = "Option::is_none")]
43        data: Option<String>,
44        #[serde(skip_serializing_if = "Option::is_none")]
45        uri: Option<String>,
46        #[serde(skip_serializing_if = "Option::is_none")]
47        mime_type: Option<DocumentMimeType>,
48    },
49    Video {
50        #[serde(skip_serializing_if = "Option::is_none")]
51        data: Option<String>,
52        #[serde(skip_serializing_if = "Option::is_none")]
53        uri: Option<String>,
54        #[serde(skip_serializing_if = "Option::is_none")]
55        mime_type: Option<VideoMimeType>,
56        #[serde(skip_serializing_if = "Option::is_none")]
57        resolution: Option<MediaResolution>,
58    },
59}
60
61impl InteractionContent {
62    /// Create a text content item.
63    pub fn text(text: impl Into<String>) -> Self {
64        Self::Text {
65            text: text.into(),
66            annotations: vec![],
67        }
68    }
69
70    /// Create an image content item from base64 data.
71    pub fn image(data: impl Into<String>, mime_type: ImageMimeType) -> Self {
72        Self::Image {
73            data: Some(data.into()),
74            uri: None,
75            mime_type: Some(mime_type),
76            resolution: None,
77        }
78    }
79
80    /// Create an image content item from a URI.
81    pub fn image_uri(uri: impl Into<String>, mime_type: ImageMimeType) -> Self {
82        Self::Image {
83            data: None,
84            uri: Some(uri.into()),
85            mime_type: Some(mime_type),
86            resolution: None,
87        }
88    }
89
90    /// Create an audio content item from base64 data.
91    pub fn audio(data: impl Into<String>, mime_type: AudioMimeType) -> Self {
92        Self::Audio {
93            data: Some(data.into()),
94            uri: None,
95            mime_type: Some(mime_type),
96            channels: None,
97            sample_rate: None,
98        }
99    }
100
101    /// Create a video content item from a URI.
102    pub fn video_uri(uri: impl Into<String>) -> Self {
103        Self::Video {
104            data: None,
105            uri: Some(uri.into()),
106            mime_type: None,
107            resolution: None,
108        }
109    }
110
111    /// Create a document content item from base64 data.
112    pub fn document(data: impl Into<String>, mime_type: DocumentMimeType) -> Self {
113        Self::Document {
114            data: Some(data.into()),
115            uri: None,
116            mime_type: Some(mime_type),
117        }
118    }
119}
120
121// ============================================================================
122// Annotation Types
123// ============================================================================
124
125/// Citation annotation — type-tagged polymorphic.
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
127#[serde(tag = "type", rename_all = "snake_case")]
128pub enum Annotation {
129    UrlCitation {
130        #[serde(skip_serializing_if = "Option::is_none")]
131        url: Option<String>,
132        #[serde(skip_serializing_if = "Option::is_none")]
133        title: Option<String>,
134        #[serde(skip_serializing_if = "Option::is_none")]
135        start_index: Option<i64>,
136        #[serde(skip_serializing_if = "Option::is_none")]
137        end_index: Option<i64>,
138    },
139    FileCitation {
140        #[serde(skip_serializing_if = "Option::is_none")]
141        document_uri: Option<String>,
142        #[serde(skip_serializing_if = "Option::is_none")]
143        file_name: Option<String>,
144        #[serde(skip_serializing_if = "Option::is_none")]
145        source: Option<String>,
146        #[serde(skip_serializing_if = "Option::is_none")]
147        custom_metadata: Option<serde_json::Value>,
148        #[serde(skip_serializing_if = "Option::is_none")]
149        page_number: Option<i64>,
150        #[serde(skip_serializing_if = "Option::is_none")]
151        media_id: Option<String>,
152        #[serde(skip_serializing_if = "Option::is_none")]
153        start_index: Option<i64>,
154        #[serde(skip_serializing_if = "Option::is_none")]
155        end_index: Option<i64>,
156    },
157    PlaceCitation {
158        #[serde(skip_serializing_if = "Option::is_none")]
159        place_id: Option<String>,
160        #[serde(skip_serializing_if = "Option::is_none")]
161        name: Option<String>,
162        #[serde(skip_serializing_if = "Option::is_none")]
163        url: Option<String>,
164        #[serde(skip_serializing_if = "Option::is_none")]
165        review_snippets: Option<ReviewSnippet>,
166        #[serde(skip_serializing_if = "Option::is_none")]
167        start_index: Option<i64>,
168        #[serde(skip_serializing_if = "Option::is_none")]
169        end_index: Option<i64>,
170    },
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
174pub struct ReviewSnippet {
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub title: Option<String>,
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub url: Option<String>,
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub review_id: Option<String>,
181}
182
183// ============================================================================
184// Step Types (the core of Interactions API)
185// ============================================================================
186
187/// A single step in an interaction — type-tagged polymorphic.
188///
189/// Each step represents a typed action in the interaction history:
190/// user input, model output, thoughts, function calls, tool invocations, etc.
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
192#[serde(tag = "type", rename_all = "snake_case")]
193pub enum Step {
194    UserInput {
195        #[serde(default, skip_serializing_if = "Vec::is_empty")]
196        content: Vec<InteractionContent>,
197    },
198
199    ModelOutput {
200        #[serde(default, skip_serializing_if = "Vec::is_empty")]
201        content: Vec<InteractionContent>,
202        #[serde(skip_serializing_if = "Option::is_none")]
203        error: Option<Status>,
204    },
205
206    Thought {
207        #[serde(skip_serializing_if = "Option::is_none")]
208        signature: Option<String>,
209        #[serde(default, skip_serializing_if = "Vec::is_empty")]
210        summary: Vec<ThoughtSummaryContent>,
211    },
212
213    FunctionCall {
214        name: String,
215        arguments: serde_json::Value,
216        id: String,
217    },
218
219    FunctionResult {
220        #[serde(skip_serializing_if = "Option::is_none")]
221        name: Option<String>,
222        call_id: String,
223        result: StepResult,
224        #[serde(skip_serializing_if = "Option::is_none")]
225        is_error: Option<bool>,
226    },
227
228    CodeExecutionCall {
229        arguments: CodeExecutionCallArguments,
230        id: String,
231        #[serde(skip_serializing_if = "Option::is_none")]
232        signature: Option<String>,
233    },
234
235    CodeExecutionResult {
236        result: String,
237        call_id: String,
238        #[serde(skip_serializing_if = "Option::is_none")]
239        is_error: Option<bool>,
240        #[serde(skip_serializing_if = "Option::is_none")]
241        signature: Option<String>,
242    },
243
244    UrlContextCall {
245        id: String,
246        #[serde(skip_serializing_if = "Option::is_none")]
247        signature: Option<String>,
248        arguments: UrlContextCallArguments,
249    },
250
251    UrlContextResult {
252        result: UrlContextResultData,
253        #[serde(skip_serializing_if = "Option::is_none")]
254        is_error: Option<bool>,
255        call_id: String,
256        #[serde(skip_serializing_if = "Option::is_none")]
257        signature: Option<String>,
258    },
259
260    GoogleSearchCall {
261        arguments: GoogleSearchCallArguments,
262        #[serde(skip_serializing_if = "Option::is_none")]
263        search_type: Option<GoogleSearchType>,
264        id: String,
265        #[serde(skip_serializing_if = "Option::is_none")]
266        signature: Option<String>,
267    },
268
269    GoogleSearchResult {
270        result: GoogleSearchResultItem,
271        #[serde(skip_serializing_if = "Option::is_none")]
272        is_error: Option<bool>,
273        call_id: String,
274        #[serde(skip_serializing_if = "Option::is_none")]
275        signature: Option<String>,
276    },
277
278    GoogleMapsCall {
279        #[serde(skip_serializing_if = "Option::is_none")]
280        arguments: Option<GoogleMapsCallArguments>,
281        id: String,
282        #[serde(skip_serializing_if = "Option::is_none")]
283        signature: Option<String>,
284    },
285
286    GoogleMapsResult {
287        result: GoogleMapsResultItem,
288        call_id: String,
289        #[serde(skip_serializing_if = "Option::is_none")]
290        signature: Option<String>,
291    },
292
293    FileSearchCall {
294        id: String,
295        #[serde(skip_serializing_if = "Option::is_none")]
296        signature: Option<String>,
297    },
298
299    FileSearchResult {
300        #[serde(default, skip_serializing_if = "Vec::is_empty")]
301        citations: Vec<Annotation>,
302        call_id: String,
303        #[serde(skip_serializing_if = "Option::is_none")]
304        signature: Option<String>,
305    },
306
307    McpServerToolCall {
308        name: String,
309        server_name: String,
310        arguments: serde_json::Value,
311        id: String,
312    },
313
314    McpServerToolResult {
315        #[serde(skip_serializing_if = "Option::is_none")]
316        name: Option<String>,
317        #[serde(skip_serializing_if = "Option::is_none")]
318        server_name: Option<String>,
319        call_id: String,
320        result: StepResult,
321    },
322}
323
324// ============================================================================
325// Step Sub-types
326// ============================================================================
327
328/// Function result can be a content array, JSON object, or string.
329#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
330#[serde(untagged)]
331pub enum StepResult {
332    ContentArray(Vec<StepResultContent>),
333    Object(serde_json::Value),
334    String(String),
335}
336
337impl StepResult {
338    pub fn from_string(s: impl Into<String>) -> Self {
339        Self::String(s.into())
340    }
341
342    pub fn from_json(value: serde_json::Value) -> Self {
343        Self::Object(value)
344    }
345}
346
347/// Content items allowed in step results (text or image only).
348#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
349#[serde(tag = "type", rename_all = "snake_case")]
350pub enum StepResultContent {
351    Text {
352        text: String,
353    },
354    Image {
355        #[serde(skip_serializing_if = "Option::is_none")]
356        data: Option<String>,
357        #[serde(skip_serializing_if = "Option::is_none")]
358        uri: Option<String>,
359        #[serde(skip_serializing_if = "Option::is_none")]
360        mime_type: Option<ImageMimeType>,
361    },
362}
363
364/// Thought summary content — polymorphic.
365#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
366#[serde(tag = "type", rename_all = "snake_case")]
367pub enum ThoughtSummaryContent {
368    Text { text: String },
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
372pub struct CodeExecutionCallArguments {
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub language: Option<CodeLanguage>,
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub code: Option<String>,
377}
378
379#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
380pub struct UrlContextCallArguments {
381    #[serde(default, skip_serializing_if = "Vec::is_empty")]
382    pub urls: Vec<String>,
383}
384
385#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
386pub struct GoogleSearchCallArguments {
387    #[serde(default, skip_serializing_if = "Vec::is_empty")]
388    pub queries: Vec<String>,
389}
390
391#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
392pub struct GoogleMapsCallArguments {
393    #[serde(default, skip_serializing_if = "Vec::is_empty")]
394    pub queries: Vec<String>,
395}
396
397#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
398pub struct GoogleSearchResultItem {
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub search_suggestions: Option<String>,
401}
402
403#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
404pub struct GoogleMapsResultItem {
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub places: Option<GoogleMapsResultPlaces>,
407}
408
409#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
410pub struct GoogleMapsResultPlaces {
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub place_id: Option<String>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub name: Option<String>,
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub url: Option<String>,
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub review_snippets: Option<ReviewSnippet>,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub widget_context_token: Option<String>,
421}
422
423#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
424pub struct UrlContextResultData {
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub url: Option<String>,
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub status: Option<UrlContextStatus>,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
432pub struct Status {
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub code: Option<i32>,
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub message: Option<String>,
437    #[serde(default, skip_serializing_if = "Vec::is_empty")]
438    pub details: Vec<serde_json::Value>,
439}
440
441// ============================================================================
442// Interaction Resource
443// ============================================================================
444
445/// Interaction resource — represents a complete interaction.
446#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
447#[serde(rename_all = "snake_case")]
448pub struct Interaction {
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub id: Option<String>,
451
452    #[serde(default)]
453    pub status: InteractionStatus,
454
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub model: Option<String>,
457
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub agent: Option<String>,
460
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub object: Option<String>,
463
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub created: Option<String>,
466
467    #[serde(skip_serializing_if = "Option::is_none")]
468    pub updated: Option<String>,
469
470    #[serde(default, skip_serializing_if = "Vec::is_empty")]
471    pub steps: Vec<Step>,
472
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub usage: Option<InteractionUsage>,
475
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub system_instruction: Option<String>,
478
479    #[serde(default, skip_serializing_if = "Vec::is_empty")]
480    pub tools: Vec<InteractionTool>,
481
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub previous_interaction_id: Option<String>,
484
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub environment_id: Option<String>,
487
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub response_modalities: Option<Vec<ResponseModality>>,
490
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub service_tier: Option<ServiceTier>,
493
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub cached_content: Option<String>,
496
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub agent_config: Option<AgentConfig>,
499
500    #[serde(skip_serializing_if = "Option::is_none")]
501    pub error: Option<InteractionError>,
502}
503
504/// Interaction status.
505#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
506#[serde(rename_all = "snake_case")]
507pub enum InteractionStatus {
508    #[default]
509    InProgress,
510    RequiresAction,
511    Completed,
512    Failed,
513    Cancelled,
514    Incomplete,
515    BudgetExceeded,
516}
517
518impl InteractionStatus {
519    pub fn is_terminal(&self) -> bool {
520        matches!(
521            self,
522            Self::Completed
523                | Self::Failed
524                | Self::Cancelled
525                | Self::Incomplete
526                | Self::BudgetExceeded
527        )
528    }
529
530    pub fn is_active(&self) -> bool {
531        matches!(self, Self::InProgress | Self::RequiresAction)
532    }
533}
534
535impl AsRef<str> for InteractionStatus {
536    fn as_ref(&self) -> &str {
537        match self {
538            Self::InProgress => "in_progress",
539            Self::RequiresAction => "requires_action",
540            Self::Completed => "completed",
541            Self::Failed => "failed",
542            Self::Cancelled => "cancelled",
543            Self::Incomplete => "incomplete",
544            Self::BudgetExceeded => "budget_exceeded",
545        }
546    }
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
550pub struct InteractionError {
551    #[serde(skip_serializing_if = "Option::is_none")]
552    pub code: Option<String>,
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub message: Option<String>,
555}
556
557// ============================================================================
558// Tool Types
559// ============================================================================
560
561/// Tool declaration — type-tagged polymorphic.
562#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
563#[serde(tag = "type", rename_all = "snake_case")]
564pub enum InteractionTool {
565    Function {
566        #[serde(skip_serializing_if = "Option::is_none")]
567        name: Option<String>,
568        #[serde(skip_serializing_if = "Option::is_none")]
569        description: Option<String>,
570        #[serde(skip_serializing_if = "Option::is_none")]
571        parameters: Option<serde_json::Value>,
572    },
573
574    CodeExecution,
575
576    UrlContext,
577
578    ComputerUse {
579        #[serde(skip_serializing_if = "Option::is_none")]
580        environment: Option<ComputerUseEnvironment>,
581        #[serde(default, skip_serializing_if = "Vec::is_empty")]
582        excluded_predefined_functions: Vec<String>,
583        #[serde(skip_serializing_if = "Option::is_none")]
584        enable_prompt_injection_detection: Option<bool>,
585        #[serde(default, skip_serializing_if = "Vec::is_empty")]
586        disabled_safety_policies: Vec<ComputerUseSafetyPolicy>,
587    },
588
589    McpServer {
590        #[serde(skip_serializing_if = "Option::is_none")]
591        name: Option<String>,
592        #[serde(skip_serializing_if = "Option::is_none")]
593        url: Option<String>,
594        #[serde(skip_serializing_if = "Option::is_none")]
595        headers: Option<serde_json::Value>,
596        #[serde(skip_serializing_if = "Option::is_none")]
597        allowed_tools: Option<AllowedTools>,
598    },
599
600    GoogleSearch {
601        #[serde(default, skip_serializing_if = "Vec::is_empty")]
602        search_types: Vec<GoogleSearchType>,
603    },
604
605    FileSearch {
606        #[serde(default, skip_serializing_if = "Vec::is_empty")]
607        file_search_store_names: Vec<String>,
608        #[serde(skip_serializing_if = "Option::is_none")]
609        top_k: Option<i64>,
610        #[serde(skip_serializing_if = "Option::is_none")]
611        metadata_filter: Option<String>,
612    },
613
614    GoogleMaps {
615        #[serde(skip_serializing_if = "Option::is_none")]
616        enable_widget: Option<bool>,
617        #[serde(skip_serializing_if = "Option::is_none")]
618        latitude: Option<f64>,
619        #[serde(skip_serializing_if = "Option::is_none")]
620        longitude: Option<f64>,
621    },
622
623    Retrieval {
624        #[serde(default, skip_serializing_if = "Vec::is_empty")]
625        retrieval_types: Vec<RetrievalType>,
626        #[serde(skip_serializing_if = "Option::is_none")]
627        exa_ai_search_config: Option<ExaAISearchConfig>,
628        #[serde(skip_serializing_if = "Option::is_none")]
629        parallel_ai_search_config: Option<ParallelAISearchConfig>,
630        #[serde(skip_serializing_if = "Option::is_none")]
631        rag_store_config: Option<RagStoreConfig>,
632    },
633}
634
635impl InteractionTool {
636    /// Create a function tool.
637    pub fn function(
638        name: impl Into<String>,
639        description: impl Into<String>,
640        parameters: serde_json::Value,
641    ) -> Self {
642        Self::Function {
643            name: Some(name.into()),
644            description: Some(description.into()),
645            parameters: Some(parameters),
646        }
647    }
648
649    /// Create a Google Search tool.
650    pub fn google_search() -> Self {
651        Self::GoogleSearch {
652            search_types: vec![GoogleSearchType::WebSearch],
653        }
654    }
655
656    /// Create a code execution tool.
657    pub fn code_execution() -> Self {
658        Self::CodeExecution
659    }
660
661    /// Create a URL context tool.
662    pub fn url_context() -> Self {
663        Self::UrlContext
664    }
665
666    /// Create a Google Maps tool.
667    pub fn google_maps() -> Self {
668        Self::GoogleMaps {
669            enable_widget: None,
670            latitude: None,
671            longitude: None,
672        }
673    }
674
675    /// Create a file search tool.
676    pub fn file_search(store_names: Vec<String>) -> Self {
677        Self::FileSearch {
678            file_search_store_names: store_names,
679            top_k: None,
680            metadata_filter: None,
681        }
682    }
683
684    /// Create an MCP Server tool.
685    pub fn mcp_server(name: impl Into<String>, url: impl Into<String>) -> Self {
686        Self::McpServer {
687            name: Some(name.into()),
688            url: Some(url.into()),
689            headers: None,
690            allowed_tools: None,
691        }
692    }
693
694    /// Create a computer use tool.
695    pub fn computer_use(environment: ComputerUseEnvironment) -> Self {
696        Self::ComputerUse {
697            environment: Some(environment),
698            excluded_predefined_functions: vec![],
699            enable_prompt_injection_detection: None,
700            disabled_safety_policies: vec![],
701        }
702    }
703
704    /// Create a retrieval tool.
705    pub fn retrieval(retrieval_types: Vec<RetrievalType>) -> Self {
706        Self::Retrieval {
707            retrieval_types,
708            exa_ai_search_config: None,
709            parallel_ai_search_config: None,
710            rag_store_config: None,
711        }
712    }
713}
714
715#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
716pub struct AllowedTools {
717    #[serde(skip_serializing_if = "Option::is_none")]
718    pub mode: Option<ToolChoiceMode>,
719    #[serde(default, skip_serializing_if = "Vec::is_empty")]
720    pub tools: Vec<String>,
721}
722
723#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
724pub struct ExaAISearchConfig {
725    #[serde(skip_serializing_if = "Option::is_none")]
726    pub api_key: Option<String>,
727    #[serde(skip_serializing_if = "Option::is_none")]
728    pub custom_config: Option<serde_json::Value>,
729}
730
731#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
732pub struct ParallelAISearchConfig {
733    #[serde(skip_serializing_if = "Option::is_none")]
734    pub api_key: Option<String>,
735    #[serde(skip_serializing_if = "Option::is_none")]
736    pub custom_config: Option<serde_json::Value>,
737}
738
739#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
740pub struct RagStoreConfig {
741    #[serde(skip_serializing_if = "Option::is_none")]
742    pub rag_resources: Option<RagResource>,
743}
744
745#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
746pub struct RagResource {
747    #[serde(skip_serializing_if = "Option::is_none")]
748    pub rag_corpus: Option<String>,
749    #[serde(default, skip_serializing_if = "Vec::is_empty")]
750    pub rag_file_ids: Vec<String>,
751    #[serde(skip_serializing_if = "Option::is_none")]
752    pub rag_retrieval_config: Option<RagRetrievalConfig>,
753}
754
755/// Retrieval configuration for RAG queries.
756#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
757pub struct RagRetrievalConfig {
758    #[serde(skip_serializing_if = "Option::is_none")]
759    pub top_k: Option<i64>,
760    #[serde(skip_serializing_if = "Option::is_none")]
761    pub hybrid_search: Option<HybridSearch>,
762    #[serde(skip_serializing_if = "Option::is_none")]
763    pub filter: Option<RagFilter>,
764    #[serde(skip_serializing_if = "Option::is_none")]
765    pub ranking: Option<Ranking>,
766}
767
768/// Hybrid search configuration.
769#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
770pub struct HybridSearch {
771    #[serde(skip_serializing_if = "Option::is_none")]
772    pub alpha: Option<f64>,
773}
774
775/// Filter configuration for RAG retrieval.
776#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
777pub struct RagFilter {
778    #[serde(skip_serializing_if = "Option::is_none")]
779    pub vector_distance_threshold: Option<f64>,
780    #[serde(skip_serializing_if = "Option::is_none")]
781    pub vector_similarity_threshold: Option<f64>,
782    #[serde(skip_serializing_if = "Option::is_none")]
783    pub metadata_filter: Option<String>,
784}
785
786/// Ranking and reranking configuration.
787#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
788pub struct Ranking {
789    #[serde(flatten)]
790    pub config: Option<serde_json::Value>,
791}
792
793// ============================================================================
794// Response Format Types
795// ============================================================================
796
797/// Response format configuration — type-tagged polymorphic.
798#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
799#[serde(tag = "type", rename_all = "snake_case")]
800pub enum ResponseFormat {
801    Text {
802        #[serde(skip_serializing_if = "Option::is_none")]
803        mime_type: Option<TextMimeType>,
804        #[serde(skip_serializing_if = "Option::is_none")]
805        schema: Option<serde_json::Value>,
806    },
807    Audio {
808        #[serde(skip_serializing_if = "Option::is_none")]
809        mime_type: Option<AudioOutputMimeType>,
810        #[serde(skip_serializing_if = "Option::is_none")]
811        delivery: Option<DeliveryMode>,
812        #[serde(skip_serializing_if = "Option::is_none")]
813        sample_rate: Option<i32>,
814        #[serde(skip_serializing_if = "Option::is_none")]
815        bit_rate: Option<i32>,
816    },
817    Image {
818        #[serde(skip_serializing_if = "Option::is_none")]
819        mime_type: Option<ImageOutputMimeType>,
820        #[serde(skip_serializing_if = "Option::is_none")]
821        delivery: Option<DeliveryMode>,
822        #[serde(skip_serializing_if = "Option::is_none")]
823        aspect_ratio: Option<AspectRatio>,
824        #[serde(skip_serializing_if = "Option::is_none")]
825        image_size: Option<ImageSize>,
826    },
827    Video {
828        #[serde(skip_serializing_if = "Option::is_none")]
829        delivery: Option<DeliveryMode>,
830        #[serde(skip_serializing_if = "Option::is_none")]
831        gcs_uri: Option<String>,
832        #[serde(skip_serializing_if = "Option::is_none")]
833        aspect_ratio: Option<VideoAspectRatio>,
834        #[serde(skip_serializing_if = "Option::is_none")]
835        duration: Option<String>,
836    },
837}
838
839impl ResponseFormat {
840    /// JSON structured output with a schema.
841    pub fn json_schema(schema: serde_json::Value) -> Self {
842        Self::Text {
843            mime_type: Some(TextMimeType::ApplicationJson),
844            schema: Some(schema),
845        }
846    }
847
848    /// Plain text output.
849    pub fn text() -> Self {
850        Self::Text {
851            mime_type: Some(TextMimeType::TextPlain),
852            schema: None,
853        }
854    }
855}
856
857// ============================================================================
858// Generation Config (Interactions version)
859// ============================================================================
860
861/// Model interaction configuration (mutually exclusive with agent_config).
862#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
863#[serde(rename_all = "snake_case")]
864pub struct InteractionGenerationConfig {
865    #[serde(skip_serializing_if = "Option::is_none")]
866    pub temperature: Option<f64>,
867
868    #[serde(skip_serializing_if = "Option::is_none")]
869    pub top_p: Option<f64>,
870
871    #[serde(skip_serializing_if = "Option::is_none")]
872    pub seed: Option<i64>,
873
874    #[serde(default, skip_serializing_if = "Vec::is_empty")]
875    pub stop_sequences: Vec<String>,
876
877    #[serde(skip_serializing_if = "Option::is_none")]
878    pub thinking_level: Option<InteractionThinkingLevel>,
879
880    #[serde(skip_serializing_if = "Option::is_none")]
881    pub thinking_summaries: Option<ThinkingSummaries>,
882
883    #[serde(skip_serializing_if = "Option::is_none")]
884    pub max_output_tokens: Option<i64>,
885
886    #[serde(default, skip_serializing_if = "Vec::is_empty")]
887    pub speech_config: Vec<InteractionSpeechConfig>,
888
889    #[serde(skip_serializing_if = "Option::is_none")]
890    pub video_config: Option<VideoConfig>,
891
892    #[serde(skip_serializing_if = "Option::is_none")]
893    pub presence_penalty: Option<f64>,
894
895    #[serde(skip_serializing_if = "Option::is_none")]
896    pub frequency_penalty: Option<f64>,
897
898    #[serde(skip_serializing_if = "Option::is_none")]
899    pub tool_choice: Option<ToolChoiceConfig>,
900}
901
902#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
903#[serde(rename_all = "snake_case")]
904pub enum InteractionThinkingLevel {
905    Minimal,
906    Low,
907    Medium,
908    High,
909}
910
911#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
912#[serde(rename_all = "snake_case")]
913pub enum ThinkingSummaries {
914    Auto,
915    None,
916}
917
918#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
919#[serde(rename_all = "snake_case")]
920pub struct InteractionSpeechConfig {
921    #[serde(skip_serializing_if = "Option::is_none")]
922    pub voice: Option<String>,
923    #[serde(skip_serializing_if = "Option::is_none")]
924    pub language: Option<String>,
925    #[serde(skip_serializing_if = "Option::is_none")]
926    pub speaker: Option<String>,
927}
928
929#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
930#[serde(rename_all = "snake_case")]
931pub struct VideoConfig {
932    #[serde(skip_serializing_if = "Option::is_none")]
933    pub task: Option<VideoTask>,
934}
935
936#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
937pub struct ToolChoiceConfig {
938    #[serde(skip_serializing_if = "Option::is_none")]
939    pub allowed_tools: Option<AllowedTools>,
940}
941
942// ============================================================================
943// Usage Types
944// ============================================================================
945
946/// Token usage statistics.
947#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
948#[serde(rename_all = "snake_case")]
949pub struct InteractionUsage {
950    #[serde(skip_serializing_if = "Option::is_none")]
951    pub total_input_tokens: Option<i64>,
952
953    #[serde(default, skip_serializing_if = "Vec::is_empty")]
954    pub input_tokens_by_modality: Vec<ModalityTokens>,
955
956    #[serde(skip_serializing_if = "Option::is_none")]
957    pub total_cached_tokens: Option<i64>,
958
959    #[serde(default, skip_serializing_if = "Vec::is_empty")]
960    pub cached_tokens_by_modality: Vec<ModalityTokens>,
961
962    #[serde(skip_serializing_if = "Option::is_none")]
963    pub total_output_tokens: Option<i64>,
964
965    #[serde(default, skip_serializing_if = "Vec::is_empty")]
966    pub output_tokens_by_modality: Vec<ModalityTokens>,
967
968    #[serde(skip_serializing_if = "Option::is_none")]
969    pub total_tool_use_tokens: Option<i64>,
970
971    #[serde(default, skip_serializing_if = "Vec::is_empty")]
972    pub tool_use_tokens_by_modality: Vec<ModalityTokens>,
973
974    #[serde(skip_serializing_if = "Option::is_none")]
975    pub total_thought_tokens: Option<i64>,
976
977    #[serde(skip_serializing_if = "Option::is_none")]
978    pub total_tokens: Option<i64>,
979
980    #[serde(default, skip_serializing_if = "Vec::is_empty")]
981    pub grounding_tool_count: Vec<GroundingToolCount>,
982}
983
984#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
985pub struct ModalityTokens {
986    #[serde(skip_serializing_if = "Option::is_none")]
987    pub modality: Option<ResponseModality>,
988    #[serde(skip_serializing_if = "Option::is_none")]
989    pub tokens: Option<i64>,
990}
991
992#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
993pub struct GroundingToolCount {
994    #[serde(rename = "type")]
995    pub grounding_type: Option<GroundingType>,
996    #[serde(skip_serializing_if = "Option::is_none")]
997    pub count: Option<i64>,
998}
999
1000// ============================================================================
1001// Agent Config Types
1002// ============================================================================
1003
1004/// Agent configuration (mutually exclusive with generation_config).
1005#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1006#[serde(tag = "type", rename_all = "kebab-case")]
1007pub enum AgentConfig {
1008    Dynamic,
1009
1010    DeepResearch {
1011        #[serde(skip_serializing_if = "Option::is_none")]
1012        thinking_summaries: Option<ThinkingSummaries>,
1013        #[serde(skip_serializing_if = "Option::is_none")]
1014        visualization: Option<Visualization>,
1015        #[serde(skip_serializing_if = "Option::is_none")]
1016        collaborative_planning: Option<bool>,
1017        #[serde(skip_serializing_if = "Option::is_none")]
1018        enable_bigquery_tool: Option<bool>,
1019    },
1020}
1021
1022// ============================================================================
1023// Enum Types
1024// ============================================================================
1025
1026#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1027#[serde(rename_all = "snake_case")]
1028pub enum ResponseModality {
1029    Text,
1030    Image,
1031    Audio,
1032    Video,
1033    Document,
1034}
1035
1036#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1037#[serde(rename_all = "snake_case")]
1038pub enum ServiceTier {
1039    Flex,
1040    Standard,
1041    Priority,
1042}
1043
1044#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1045#[serde(rename_all = "snake_case")]
1046pub enum DeliveryMode {
1047    Inline,
1048    Uri,
1049}
1050
1051#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1052#[serde(rename_all = "snake_case")]
1053pub enum MediaResolution {
1054    Low,
1055    Medium,
1056    High,
1057    UltraHigh,
1058}
1059
1060#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1061pub enum ImageMimeType {
1062    #[serde(rename = "image/png")]
1063    Png,
1064    #[serde(rename = "image/jpeg")]
1065    Jpeg,
1066    #[serde(rename = "image/webp")]
1067    Webp,
1068    #[serde(rename = "image/heic")]
1069    Heic,
1070    #[serde(rename = "image/heif")]
1071    Heif,
1072    #[serde(rename = "image/gif")]
1073    Gif,
1074    #[serde(rename = "image/bmp")]
1075    Bmp,
1076    #[serde(rename = "image/tiff")]
1077    Tiff,
1078}
1079
1080#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1081pub enum AudioMimeType {
1082    #[serde(rename = "audio/wav")]
1083    Wav,
1084    #[serde(rename = "audio/mp3")]
1085    Mp3,
1086    #[serde(rename = "audio/aac")]
1087    Aac,
1088    #[serde(rename = "audio/flac")]
1089    Flac,
1090    #[serde(rename = "audio/ogg")]
1091    Ogg,
1092    #[serde(rename = "audio/ogg_opus")]
1093    OggOpus,
1094    #[serde(rename = "audio/m4a")]
1095    M4a,
1096    #[serde(rename = "audio/opus")]
1097    Opus,
1098    #[serde(rename = "audio/pcm")]
1099    Pcm,
1100    #[serde(rename = "audio/l16")]
1101    L16,
1102    #[serde(rename = "audio/mulaw")]
1103    Mulaw,
1104    #[serde(rename = "audio/alaw")]
1105    Alaw,
1106    #[serde(rename = "audio/amr")]
1107    Amr,
1108    #[serde(rename = "audio/aiff")]
1109    Aiff,
1110    #[serde(rename = "audio/mpeg")]
1111    Mpeg,
1112}
1113
1114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1115pub enum VideoMimeType {
1116    #[serde(rename = "video/mp4")]
1117    Mp4,
1118    #[serde(rename = "video/webm")]
1119    Webm,
1120    #[serde(rename = "video/quicktime")]
1121    Quicktime,
1122    #[serde(rename = "video/mpeg")]
1123    Mpeg,
1124    #[serde(rename = "video/x-ms-wmv")]
1125    Wmv,
1126    #[serde(rename = "video/x-flv")]
1127    Flv,
1128    #[serde(rename = "video/3gpp")]
1129    ThreeGpp,
1130    #[serde(rename = "video/avi")]
1131    Avi,
1132}
1133
1134#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1135pub enum DocumentMimeType {
1136    #[serde(rename = "application/pdf")]
1137    Pdf,
1138    #[serde(rename = "text/plain")]
1139    TextPlain,
1140    #[serde(rename = "text/html")]
1141    TextHtml,
1142    #[serde(rename = "text/css")]
1143    TextCss,
1144    #[serde(rename = "text/csv")]
1145    TextCsv,
1146    #[serde(rename = "text/javascript")]
1147    TextJavascript,
1148    #[serde(rename = "text/x-typescript")]
1149    TextXTypescript,
1150    #[serde(rename = "application/json")]
1151    ApplicationJson,
1152    #[serde(rename = "application/rtf")]
1153    Rtf,
1154    #[serde(rename = "application/vnd.openxmlformats-officedocument.wordprocessingml.document")]
1155    Docx,
1156    #[serde(rename = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")]
1157    Xlsx,
1158    #[serde(rename = "application/vnd.openxmlformats-officedocument.presentationml.presentation")]
1159    Pptx,
1160    #[serde(rename = "application/vnd.ms-excel")]
1161    Xls,
1162    #[serde(rename = "application/vnd.ms-powerpoint")]
1163    Ppt,
1164    #[serde(rename = "application/msword")]
1165    Doc,
1166    #[serde(rename = "text/markdown")]
1167    TextMarkdown,
1168    #[serde(rename = "text/x-python")]
1169    TextXPython,
1170    #[serde(rename = "application/x-java-source")]
1171    ApplicationXJavaSource,
1172    #[serde(rename = "application/x-sh")]
1173    ApplicationXSh,
1174    #[serde(rename = "application/typescript")]
1175    ApplicationTypescript,
1176    #[serde(rename = "application/x-typescript")]
1177    ApplicationXTypescript,
1178    #[serde(rename = "application/javascript")]
1179    ApplicationJavascript,
1180}
1181
1182#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1183pub enum TextMimeType {
1184    #[serde(rename = "text/plain")]
1185    TextPlain,
1186    #[serde(rename = "application/json")]
1187    ApplicationJson,
1188}
1189
1190#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1191pub enum AudioOutputMimeType {
1192    #[serde(rename = "audio/mp3")]
1193    Mp3,
1194    #[serde(rename = "audio/ogg_opus")]
1195    OggOpus,
1196    #[serde(rename = "audio/l16")]
1197    L16,
1198    #[serde(rename = "audio/wav")]
1199    Wav,
1200    #[serde(rename = "audio/alaw")]
1201    Alaw,
1202    #[serde(rename = "audio/mulaw")]
1203    Mulaw,
1204}
1205
1206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1207pub enum ImageOutputMimeType {
1208    #[serde(rename = "image/jpeg")]
1209    Jpeg,
1210    #[serde(rename = "image/png")]
1211    Png,
1212}
1213
1214#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1215pub enum AspectRatio {
1216    #[serde(rename = "1:1")]
1217    R1x1,
1218    #[serde(rename = "2:3")]
1219    R2x3,
1220    #[serde(rename = "3:2")]
1221    R3x2,
1222    #[serde(rename = "3:4")]
1223    R3x4,
1224    #[serde(rename = "4:3")]
1225    R4x3,
1226    #[serde(rename = "4:5")]
1227    R4x5,
1228    #[serde(rename = "5:4")]
1229    R5x4,
1230    #[serde(rename = "9:16")]
1231    R9x16,
1232    #[serde(rename = "16:9")]
1233    R16x9,
1234    #[serde(rename = "21:9")]
1235    R21x9,
1236    #[serde(rename = "1:8")]
1237    R1x8,
1238    #[serde(rename = "8:1")]
1239    R8x1,
1240    #[serde(rename = "1:4")]
1241    R1x4,
1242    #[serde(rename = "4:1")]
1243    R4x1,
1244}
1245
1246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1247#[serde(rename_all = "snake_case")]
1248pub enum ImageSize {
1249    #[serde(rename = "512")]
1250    S512,
1251    #[serde(rename = "1K")]
1252    S1k,
1253    #[serde(rename = "2K")]
1254    S2k,
1255    #[serde(rename = "4K")]
1256    S4k,
1257}
1258
1259#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1260pub enum VideoAspectRatio {
1261    #[serde(rename = "16:9")]
1262    R16x9,
1263    #[serde(rename = "9:16")]
1264    R9x16,
1265}
1266
1267#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1268#[serde(rename_all = "snake_case")]
1269pub enum VideoTask {
1270    TextToVideo,
1271    ImageToVideo,
1272    ReferenceToVideo,
1273    Edit,
1274}
1275
1276#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1277#[serde(rename_all = "snake_case")]
1278pub enum CodeLanguage {
1279    Python,
1280}
1281
1282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1283#[serde(rename_all = "snake_case")]
1284pub enum GoogleSearchType {
1285    WebSearch,
1286    ImageSearch,
1287    EnterpriseWebSearch,
1288}
1289
1290#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1291#[serde(rename_all = "snake_case")]
1292pub enum RetrievalType {
1293    RagStore,
1294    ExaAiSearch,
1295    ParallelAiSearch,
1296}
1297
1298#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1299#[serde(rename_all = "snake_case")]
1300pub enum ComputerUseEnvironment {
1301    Browser,
1302    Mobile,
1303    Desktop,
1304}
1305
1306#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1307#[serde(rename_all = "snake_case")]
1308pub enum ComputerUseSafetyPolicy {
1309    DangerousContent,
1310    Harassment,
1311    HateSpeech,
1312    SexuallyExplicit,
1313    CivilIntegrity,
1314    Csam,
1315    TributeSpeech,
1316    Unspecified,
1317}
1318
1319#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1320#[serde(rename_all = "snake_case")]
1321pub enum UrlContextStatus {
1322    Success,
1323    Error,
1324    Paywall,
1325    Unsafe,
1326}
1327
1328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1329#[serde(rename_all = "snake_case")]
1330pub enum ToolChoiceMode {
1331    Auto,
1332    Any,
1333    None,
1334    Validated,
1335}
1336
1337#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1338#[serde(rename_all = "snake_case")]
1339pub enum Visualization {
1340    Off,
1341    Auto,
1342}
1343
1344#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1345#[serde(rename_all = "snake_case")]
1346pub enum GroundingType {
1347    GoogleSearch,
1348    GoogleMaps,
1349    Retrieval,
1350}
1351
1352// ============================================================================
1353// Webhook Config
1354// ============================================================================
1355
1356#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1357#[serde(rename_all = "snake_case")]
1358pub struct WebhookConfig {
1359    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1360    pub uris: Vec<String>,
1361    #[serde(skip_serializing_if = "Option::is_none")]
1362    pub user_metadata: Option<serde_json::Value>,
1363}
1364
1365// ============================================================================
1366// Environment Config
1367// ============================================================================
1368
1369/// Environment configuration — can be a full config object or just an ID string.
1370#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1371#[serde(untagged)]
1372pub enum EnvironmentConfigOrString {
1373    Config(EnvironmentConfig),
1374    Id(String),
1375}
1376
1377/// Environment configuration — type-tagged polymorphic.
1378#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1379#[serde(tag = "type", rename_all = "snake_case")]
1380pub enum EnvironmentConfig {
1381    Remote {
1382        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1383        sources: Vec<EnvironmentSource>,
1384        #[serde(skip_serializing_if = "Option::is_none")]
1385        environment_id: Option<String>,
1386        #[serde(skip_serializing_if = "Option::is_none")]
1387        network: Option<EnvironmentNetwork>,
1388    },
1389}
1390
1391/// Environment source — type-tagged polymorphic.
1392#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1393#[serde(tag = "type", rename_all = "snake_case")]
1394pub enum EnvironmentSource {
1395    Gcs {
1396        #[serde(skip_serializing_if = "Option::is_none")]
1397        source: Option<String>,
1398        #[serde(skip_serializing_if = "Option::is_none")]
1399        target: Option<String>,
1400    },
1401    Inline {
1402        #[serde(skip_serializing_if = "Option::is_none")]
1403        target: Option<String>,
1404        #[serde(skip_serializing_if = "Option::is_none")]
1405        content: Option<String>,
1406        #[serde(skip_serializing_if = "Option::is_none")]
1407        encoding: Option<String>,
1408    },
1409    Repository {
1410        #[serde(skip_serializing_if = "Option::is_none")]
1411        source: Option<String>,
1412        #[serde(skip_serializing_if = "Option::is_none")]
1413        target: Option<String>,
1414    },
1415    SkillRegistry {
1416        #[serde(skip_serializing_if = "Option::is_none")]
1417        source: Option<String>,
1418        #[serde(skip_serializing_if = "Option::is_none")]
1419        target: Option<String>,
1420    },
1421}
1422
1423/// Network configuration — can be an allowlist object or "disabled" string.
1424#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1425#[serde(untagged)]
1426pub enum EnvironmentNetwork {
1427    Allowlist(EnvironmentNetworkEgressAllowlist),
1428    Disabled(String),
1429}
1430
1431#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1432pub struct EnvironmentNetworkEgressAllowlist {
1433    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1434    pub allowlist: Vec<AllowlistEntry>,
1435}
1436
1437#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1438pub struct AllowlistEntry {
1439    #[serde(skip_serializing_if = "Option::is_none")]
1440    pub domain: Option<String>,
1441    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1442    pub transform: Vec<serde_json::Value>,
1443}
1444
1445// ============================================================================
1446// CreateInteractionRequest
1447// ============================================================================
1448
1449/// Request body for creating an interaction.
1450#[derive(Debug, Clone, Serialize, Deserialize)]
1451#[serde(rename_all = "snake_case")]
1452pub struct CreateInteractionRequest {
1453    #[serde(skip_serializing_if = "Option::is_none")]
1454    pub model: Option<String>,
1455
1456    #[serde(skip_serializing_if = "Option::is_none")]
1457    pub agent: Option<String>,
1458
1459    pub input: InteractionInput,
1460
1461    #[serde(skip_serializing_if = "Option::is_none")]
1462    pub system_instruction: Option<String>,
1463
1464    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1465    pub tools: Vec<InteractionTool>,
1466
1467    #[serde(skip_serializing_if = "Option::is_none")]
1468    pub response_format: Option<ResponseFormat>,
1469
1470    #[serde(skip_serializing_if = "Option::is_none")]
1471    pub stream: Option<bool>,
1472
1473    #[serde(skip_serializing_if = "Option::is_none")]
1474    pub store: Option<bool>,
1475
1476    #[serde(skip_serializing_if = "Option::is_none")]
1477    pub background: Option<bool>,
1478
1479    #[serde(skip_serializing_if = "Option::is_none")]
1480    pub generation_config: Option<InteractionGenerationConfig>,
1481
1482    #[serde(skip_serializing_if = "Option::is_none")]
1483    pub agent_config: Option<AgentConfig>,
1484
1485    #[serde(skip_serializing_if = "Option::is_none")]
1486    pub previous_interaction_id: Option<String>,
1487
1488    #[serde(skip_serializing_if = "Option::is_none")]
1489    pub environment: Option<EnvironmentConfigOrString>,
1490
1491    #[serde(skip_serializing_if = "Option::is_none")]
1492    pub cached_content: Option<String>,
1493
1494    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1495    pub response_modalities: Vec<ResponseModality>,
1496
1497    #[serde(skip_serializing_if = "Option::is_none")]
1498    pub service_tier: Option<ServiceTier>,
1499
1500    #[serde(skip_serializing_if = "Option::is_none")]
1501    pub webhook_config: Option<WebhookConfig>,
1502}
1503
1504/// Input can be a string, single Content, Content array, or Step array.
1505#[derive(Debug, Clone, Serialize, Deserialize)]
1506#[serde(untagged)]
1507pub enum InteractionInput {
1508    Text(String),
1509    Content(InteractionContent),
1510    ContentArray(Vec<InteractionContent>),
1511    StepArray(Vec<Step>),
1512}
1513
1514impl InteractionInput {
1515    /// Create text input.
1516    pub fn text(text: impl Into<String>) -> Self {
1517        Self::Text(text.into())
1518    }
1519
1520    /// Create content array input.
1521    pub fn content_array(content: Vec<InteractionContent>) -> Self {
1522        Self::ContentArray(content)
1523    }
1524
1525    /// Create step array input.
1526    pub fn step_array(steps: Vec<Step>) -> Self {
1527        Self::StepArray(steps)
1528    }
1529}
1530
1531impl From<String> for InteractionInput {
1532    fn from(s: String) -> Self {
1533        Self::Text(s)
1534    }
1535}
1536
1537impl From<&str> for InteractionInput {
1538    fn from(s: &str) -> Self {
1539        Self::Text(s.to_string())
1540    }
1541}
1542
1543impl From<Vec<InteractionContent>> for InteractionInput {
1544    fn from(c: Vec<InteractionContent>) -> Self {
1545        Self::ContentArray(c)
1546    }
1547}
1548
1549impl From<Vec<Step>> for InteractionInput {
1550    fn from(s: Vec<Step>) -> Self {
1551        Self::StepArray(s)
1552    }
1553}