1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4
5use crate::http::HttpOptions;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(untagged)]
10#[allow(clippy::large_enum_variant)]
11pub enum InteractionInput {
12 Text(String),
13 Content(InteractionContent),
14 Contents(Vec<InteractionContent>),
15 Turns(Vec<InteractionTurn>),
16}
17
18impl InteractionInput {
19 #[must_use]
20 pub fn text(text: impl Into<String>) -> Self {
21 Self::Text(text.into())
22 }
23
24 #[must_use]
25 pub const fn content(content: InteractionContent) -> Self {
26 Self::Content(content)
27 }
28
29 #[must_use]
30 pub const fn contents(contents: Vec<InteractionContent>) -> Self {
31 Self::Contents(contents)
32 }
33
34 #[must_use]
35 pub const fn turns(turns: Vec<InteractionTurn>) -> Self {
36 Self::Turns(turns)
37 }
38}
39
40impl From<String> for InteractionInput {
41 fn from(value: String) -> Self {
42 Self::Text(value)
43 }
44}
45
46impl From<&str> for InteractionInput {
47 fn from(value: &str) -> Self {
48 Self::Text(value.to_string())
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, Default)]
54#[serde(rename_all = "snake_case")]
55pub struct InteractionContent {
56 #[serde(rename = "type")]
57 pub content_type: String,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub text: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub annotations: Option<Vec<Value>>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub data: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub uri: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub mime_type: Option<String>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub id: Option<String>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub name: Option<String>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub arguments: Option<Value>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub result: Option<Value>,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub call_id: Option<String>,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub is_error: Option<bool>,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub code: Option<String>,
82 #[serde(skip_serializing_if = "Option::is_none")]
83 pub language: Option<String>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub summary: Option<Value>,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub urls: Option<Vec<String>>,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub queries: Option<Vec<String>>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub rendered_content: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub title: Option<String>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub url: Option<String>,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub resolution: Option<String>,
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub signature: Option<String>,
100 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
101 #[serde(flatten)]
102 pub extra: HashMap<String, Value>,
103}
104
105impl InteractionContent {
106 pub fn text(text: impl Into<String>) -> Self {
107 Self {
108 content_type: "text".into(),
109 text: Some(text.into()),
110 ..Default::default()
111 }
112 }
113
114 pub fn image_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
115 Self {
116 content_type: "image".into(),
117 data: Some(data.into()),
118 mime_type: Some(mime_type.into()),
119 ..Default::default()
120 }
121 }
122
123 pub fn audio_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
124 Self {
125 content_type: "audio".into(),
126 data: Some(data.into()),
127 mime_type: Some(mime_type.into()),
128 ..Default::default()
129 }
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "snake_case")]
136pub struct InteractionTurn {
137 pub role: String,
138 pub content: InteractionTurnContent,
139 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
140 #[serde(flatten)]
141 pub extra: HashMap<String, Value>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146#[serde(untagged)]
147pub enum InteractionTurnContent {
148 Text(String),
149 Contents(Vec<InteractionContent>),
150}
151
152impl InteractionTurnContent {
153 #[must_use]
154 pub fn text(text: impl Into<String>) -> Self {
155 Self::Text(text.into())
156 }
157
158 #[must_use]
159 pub const fn contents(contents: Vec<InteractionContent>) -> Self {
160 Self::Contents(contents)
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
166#[serde(rename_all = "lowercase")]
167pub enum InteractionResponseModality {
168 Text,
169 Image,
170 Audio,
171 Video,
172 Document,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
177#[serde(rename_all = "lowercase")]
178pub enum InteractionThinkingLevel {
179 Minimal,
180 Low,
181 Medium,
182 High,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
187#[serde(rename_all = "lowercase")]
188pub enum InteractionThinkingSummaries {
189 Auto,
190 #[serde(rename = "none")]
191 NoneValue,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
196#[serde(rename_all = "lowercase")]
197pub enum ToolChoiceType {
198 Auto,
199 Any,
200 #[serde(rename = "none")]
201 NoneValue,
202 Validated,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize, Default)]
207#[serde(rename_all = "snake_case")]
208pub struct AllowedTools {
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub mode: Option<ToolChoiceType>,
212 #[serde(skip_serializing_if = "Option::is_none")]
214 pub tools: Option<Vec<String>>,
215 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
217 #[serde(flatten)]
218 pub extra: HashMap<String, Value>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize, Default)]
223#[serde(rename_all = "snake_case")]
224pub struct ToolChoiceConfig {
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub allowed_tools: Option<AllowedTools>,
227 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
229 #[serde(flatten)]
230 pub extra: HashMap<String, Value>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235#[serde(untagged)]
236pub enum ToolChoice {
237 Type(ToolChoiceType),
238 Config(ToolChoiceConfig),
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize, Default)]
243pub struct ComputerUseTool {
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub environment: Option<String>,
246 #[serde(rename = "excludedPredefinedFunctions")]
247 #[serde(skip_serializing_if = "Option::is_none")]
248 pub excluded_predefined_functions: Option<Vec<String>>,
249 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
251 #[serde(flatten)]
252 pub extra: HashMap<String, Value>,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize, Default)]
257#[serde(rename_all = "snake_case")]
258pub struct McpServerTool {
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub allowed_tools: Option<Vec<AllowedTools>>,
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub headers: Option<HashMap<String, String>>,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub name: Option<String>,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub url: Option<String>,
267 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
269 #[serde(flatten)]
270 pub extra: HashMap<String, Value>,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize, Default)]
275#[serde(rename_all = "snake_case")]
276pub struct FileSearchTool {
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub file_search_store_names: Option<Vec<String>>,
279 #[serde(skip_serializing_if = "Option::is_none")]
280 pub metadata_filter: Option<String>,
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub top_k: Option<i32>,
283 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
285 #[serde(flatten)]
286 pub extra: HashMap<String, Value>,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize, Default)]
291pub struct FunctionTool {
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub description: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub name: Option<String>,
296 #[serde(skip_serializing_if = "Option::is_none")]
298 pub parameters: Option<Value>,
299 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
301 #[serde(flatten)]
302 pub extra: HashMap<String, Value>,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
307#[serde(tag = "type")]
308pub enum Tool {
309 #[serde(rename = "function")]
310 Function(FunctionTool),
311 #[serde(rename = "google_search")]
312 GoogleSearch,
313 #[serde(rename = "code_execution")]
314 CodeExecution,
315 #[serde(rename = "url_context")]
316 UrlContext,
317 #[serde(rename = "computer_use")]
318 ComputerUse(ComputerUseTool),
319 #[serde(rename = "mcp_server")]
320 McpServer(McpServerTool),
321 #[serde(rename = "file_search")]
322 FileSearch(FileSearchTool),
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize, Default)]
327#[serde(rename_all = "snake_case")]
328pub struct SpeechConfig {
329 #[serde(skip_serializing_if = "Option::is_none")]
330 pub voice: Option<String>,
331 #[serde(skip_serializing_if = "Option::is_none")]
332 pub language: Option<String>,
333 #[serde(skip_serializing_if = "Option::is_none")]
334 pub speaker: Option<String>,
335 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
337 #[serde(flatten)]
338 pub extra: HashMap<String, Value>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, Default)]
343#[serde(rename_all = "snake_case")]
344pub struct ImageConfig {
345 #[serde(skip_serializing_if = "Option::is_none")]
346 pub aspect_ratio: Option<String>,
347 #[serde(skip_serializing_if = "Option::is_none")]
348 pub image_size: Option<String>,
349 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
351 #[serde(flatten)]
352 pub extra: HashMap<String, Value>,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize, Default)]
357#[serde(rename_all = "snake_case")]
358pub struct GenerationConfig {
359 #[serde(skip_serializing_if = "Option::is_none")]
360 pub temperature: Option<f32>,
361 #[serde(skip_serializing_if = "Option::is_none")]
362 pub top_p: Option<f32>,
363 #[serde(skip_serializing_if = "Option::is_none")]
364 pub seed: Option<i32>,
365 #[serde(skip_serializing_if = "Option::is_none")]
366 pub stop_sequences: Option<Vec<String>>,
367 #[serde(skip_serializing_if = "Option::is_none")]
368 pub tool_choice: Option<ToolChoice>,
369 #[serde(skip_serializing_if = "Option::is_none")]
370 pub thinking_level: Option<InteractionThinkingLevel>,
371 #[serde(skip_serializing_if = "Option::is_none")]
372 pub thinking_summaries: Option<InteractionThinkingSummaries>,
373 #[serde(skip_serializing_if = "Option::is_none")]
374 pub max_output_tokens: Option<i32>,
375 #[serde(skip_serializing_if = "Option::is_none")]
376 pub speech_config: Option<Vec<SpeechConfig>>,
377 #[serde(skip_serializing_if = "Option::is_none")]
378 pub image_config: Option<ImageConfig>,
379 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
381 #[serde(flatten)]
382 pub extra: HashMap<String, Value>,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize, Default)]
387pub struct DynamicAgentConfig {
388 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
390 #[serde(flatten)]
391 pub extra: HashMap<String, Value>,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize, Default)]
396#[serde(rename_all = "snake_case")]
397pub struct DeepResearchAgentConfig {
398 #[serde(skip_serializing_if = "Option::is_none")]
399 pub thinking_summaries: Option<InteractionThinkingSummaries>,
400 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
402 #[serde(flatten)]
403 pub extra: HashMap<String, Value>,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
408#[serde(tag = "type")]
409pub enum AgentConfig {
410 #[serde(rename = "dynamic")]
411 Dynamic(DynamicAgentConfig),
412 #[serde(rename = "deep-research")]
413 DeepResearch(DeepResearchAgentConfig),
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize)]
418#[serde(rename_all = "snake_case")]
419pub struct CreateInteractionConfig {
420 #[serde(skip_serializing, skip_deserializing)]
422 pub http_options: Option<HttpOptions>,
423 #[serde(skip_serializing_if = "Option::is_none")]
425 pub model: Option<String>,
426 #[serde(skip_serializing_if = "Option::is_none")]
428 pub agent: Option<String>,
429 pub input: InteractionInput,
430 #[serde(skip_serializing_if = "Option::is_none")]
431 pub tools: Option<Vec<Tool>>,
432 #[serde(skip_serializing_if = "Option::is_none")]
433 pub generation_config: Option<GenerationConfig>,
434 #[serde(skip_serializing_if = "Option::is_none")]
435 pub agent_config: Option<AgentConfig>,
436 #[serde(skip_serializing_if = "Option::is_none")]
437 pub background: Option<bool>,
438 #[serde(skip_serializing_if = "Option::is_none")]
439 pub store: Option<bool>,
440 #[serde(skip_serializing_if = "Option::is_none")]
441 pub previous_interaction_id: Option<String>,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 pub response_format: Option<Value>,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 pub response_mime_type: Option<String>,
446 #[serde(skip_serializing_if = "Option::is_none")]
447 pub response_modalities: Option<Vec<InteractionResponseModality>>,
448 #[serde(skip_serializing_if = "Option::is_none")]
449 pub system_instruction: Option<String>,
450 #[serde(skip_serializing_if = "Option::is_none")]
451 pub stream: Option<bool>,
452}
453
454impl CreateInteractionConfig {
455 pub fn new(model: impl Into<String>, input: impl Into<InteractionInput>) -> Self {
456 Self {
457 http_options: None,
458 model: Some(model.into()),
459 agent: None,
460 input: input.into(),
461 tools: None,
462 generation_config: None,
463 agent_config: None,
464 background: None,
465 store: None,
466 previous_interaction_id: None,
467 response_format: None,
468 response_mime_type: None,
469 response_modalities: None,
470 system_instruction: None,
471 stream: None,
472 }
473 }
474
475 pub fn new_agent(agent: impl Into<String>, input: impl Into<InteractionInput>) -> Self {
476 Self {
477 http_options: None,
478 model: None,
479 agent: Some(agent.into()),
480 input: input.into(),
481 tools: None,
482 generation_config: None,
483 agent_config: None,
484 background: None,
485 store: None,
486 previous_interaction_id: None,
487 response_format: None,
488 response_mime_type: None,
489 response_modalities: None,
490 system_instruction: None,
491 stream: None,
492 }
493 }
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize, Default)]
498#[serde(rename_all = "snake_case")]
499pub struct GetInteractionConfig {
500 #[serde(skip_serializing, skip_deserializing)]
501 pub http_options: Option<HttpOptions>,
502 #[serde(skip_serializing_if = "Option::is_none")]
504 pub include_input: Option<bool>,
505 #[serde(skip_serializing_if = "Option::is_none")]
507 pub stream: Option<bool>,
508 #[serde(skip_serializing_if = "Option::is_none")]
510 pub last_event_id: Option<String>,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize, Default)]
515#[serde(rename_all = "snake_case")]
516pub struct DeleteInteractionConfig {
517 #[serde(skip_serializing, skip_deserializing)]
518 pub http_options: Option<HttpOptions>,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize, Default)]
523#[serde(rename_all = "snake_case")]
524pub struct CancelInteractionConfig {
525 #[serde(skip_serializing, skip_deserializing)]
526 pub http_options: Option<HttpOptions>,
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize, Default)]
531#[serde(rename_all = "snake_case")]
532pub struct InteractionUsageBucket {
533 #[serde(skip_serializing_if = "Option::is_none")]
534 pub text: Option<i32>,
535 #[serde(skip_serializing_if = "Option::is_none")]
536 pub image: Option<i32>,
537 #[serde(skip_serializing_if = "Option::is_none")]
538 pub audio: Option<i32>,
539 #[serde(skip_serializing_if = "Option::is_none")]
540 pub video: Option<i32>,
541 #[serde(skip_serializing_if = "Option::is_none")]
542 pub document: Option<i32>,
543 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
544 #[serde(flatten)]
545 pub extra: HashMap<String, Value>,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, Default)]
550#[serde(rename_all = "snake_case")]
551pub struct InteractionUsage {
552 #[serde(skip_serializing_if = "Option::is_none")]
553 pub total_input_tokens: Option<i32>,
554 #[serde(skip_serializing_if = "Option::is_none")]
555 pub total_output_tokens: Option<i32>,
556 #[serde(skip_serializing_if = "Option::is_none")]
557 pub total_thought_tokens: Option<i32>,
558 #[serde(skip_serializing_if = "Option::is_none")]
559 pub total_cached_tokens: Option<i32>,
560 #[serde(skip_serializing_if = "Option::is_none")]
561 pub total_tool_use_tokens: Option<i32>,
562 #[serde(skip_serializing_if = "Option::is_none")]
563 pub total_tokens: Option<i32>,
564 #[serde(skip_serializing_if = "Option::is_none")]
565 pub input_tokens_by_modality: Option<Vec<InteractionTokensByModality>>,
566 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
567 #[serde(flatten)]
568 pub extra: HashMap<String, Value>,
569}
570
571#[derive(Debug, Clone, Serialize, Deserialize, Default)]
573#[serde(rename_all = "snake_case")]
574pub struct InteractionTokensByModality {
575 #[serde(skip_serializing_if = "Option::is_none")]
576 pub modality: Option<String>,
577 #[serde(skip_serializing_if = "Option::is_none")]
578 pub tokens: Option<i32>,
579 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
580 #[serde(flatten)]
581 pub extra: HashMap<String, Value>,
582}
583
584#[derive(Debug, Clone, Serialize, Deserialize, Default)]
586#[serde(rename_all = "snake_case")]
587pub struct Interaction {
588 #[serde(skip_serializing_if = "Option::is_none")]
589 pub id: Option<String>,
590 #[serde(skip_serializing_if = "Option::is_none")]
591 pub object: Option<String>,
592 #[serde(skip_serializing_if = "Option::is_none")]
593 pub model: Option<String>,
594 #[serde(skip_serializing_if = "Option::is_none")]
595 pub agent: Option<String>,
596 #[serde(skip_serializing_if = "Option::is_none")]
597 pub role: Option<String>,
598 #[serde(skip_serializing_if = "Option::is_none")]
599 pub status: Option<String>,
600 #[serde(skip_serializing_if = "Option::is_none")]
601 pub created: Option<String>,
602 #[serde(skip_serializing_if = "Option::is_none")]
603 pub updated: Option<String>,
604 #[serde(skip_serializing_if = "Option::is_none")]
605 pub input: Option<InteractionInput>,
606 #[serde(skip_serializing_if = "Option::is_none")]
607 pub outputs: Option<Vec<InteractionContent>>,
608 #[serde(skip_serializing_if = "Option::is_none")]
609 pub usage: Option<InteractionUsage>,
610 #[serde(skip_serializing_if = "Option::is_none")]
611 pub previous_interaction_id: Option<String>,
612 #[serde(skip_serializing_if = "Option::is_none")]
613 pub background: Option<bool>,
614 #[serde(skip_serializing_if = "Option::is_none")]
615 pub store: Option<bool>,
616 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
617 #[serde(flatten)]
618 pub extra: HashMap<String, Value>,
619}
620
621#[derive(Debug, Clone, Serialize, Deserialize, Default)]
623#[serde(rename_all = "snake_case")]
624pub struct InteractionSseEvent {
625 #[serde(rename = "event_type", alias = "eventType")]
626 #[serde(skip_serializing_if = "Option::is_none")]
627 pub event_type: Option<String>,
628 #[serde(rename = "event_id", alias = "eventId")]
629 #[serde(skip_serializing_if = "Option::is_none")]
630 pub event_id: Option<String>,
631 #[serde(skip_serializing_if = "Option::is_none")]
633 pub interaction: Option<Interaction>,
634 #[serde(skip_serializing_if = "Option::is_none")]
636 pub interaction_id: Option<String>,
637 #[serde(skip_serializing_if = "Option::is_none")]
639 pub status: Option<String>,
640 #[serde(skip_serializing_if = "Option::is_none")]
642 pub index: Option<i32>,
643 #[serde(skip_serializing_if = "Option::is_none")]
645 pub content: Option<InteractionContent>,
646 #[serde(skip_serializing_if = "Option::is_none")]
648 pub delta: Option<InteractionContent>,
649 #[serde(skip_serializing_if = "Option::is_none")]
651 pub error: Option<InteractionError>,
652 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
653 #[serde(flatten)]
654 pub extra: HashMap<String, Value>,
655}
656
657#[deprecated(note = "Renamed to InteractionSseEvent")]
659pub type InteractionEvent = InteractionSseEvent;
660
661#[derive(Debug, Clone, Serialize, Deserialize, Default)]
662#[serde(rename_all = "snake_case")]
663pub struct InteractionError {
664 #[serde(skip_serializing_if = "Option::is_none")]
665 pub code: Option<String>,
666 #[serde(skip_serializing_if = "Option::is_none")]
667 pub message: Option<String>,
668 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
669 #[serde(flatten)]
670 pub extra: HashMap<String, Value>,
671}
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676
677 #[test]
678 fn input_text_serializes_as_string() {
679 let input = InteractionInput::text("hello");
680 let json = serde_json::to_string(&input).unwrap();
681 assert_eq!(json, "\"hello\"");
682 }
683
684 #[test]
685 fn input_contents_serializes_as_array() {
686 let input = InteractionInput::contents(vec![InteractionContent::text("hi")]);
687 let value = serde_json::to_value(&input).unwrap();
688 assert!(value.is_array());
689 }
690
691 #[test]
692 fn interaction_event_start_roundtrip() {
693 let json = r#"{
694 "event_type": "interaction.start",
695 "event_id": "evt_1",
696 "interaction": {
697 "id": "int_123",
698 "status": "in_progress"
699 }
700 }"#;
701 let event: InteractionSseEvent = serde_json::from_str(json).unwrap();
702 assert_eq!(event.event_type.as_deref(), Some("interaction.start"));
703 assert_eq!(event.event_id.as_deref(), Some("evt_1"));
704 assert_eq!(
705 event.interaction.as_ref().and_then(|d| d.id.as_deref()),
706 Some("int_123")
707 );
708 }
709
710 #[test]
711 fn interaction_content_helpers() {
712 let image = InteractionContent::image_data("AAAA", "image/png");
713 assert_eq!(image.content_type, "image");
714 assert_eq!(image.mime_type.as_deref(), Some("image/png"));
715
716 let audio = InteractionContent::audio_data("BBBB", "audio/wav");
717 assert_eq!(audio.content_type, "audio");
718 assert_eq!(audio.mime_type.as_deref(), Some("audio/wav"));
719 }
720
721 #[test]
722 fn interaction_input_from_str() {
723 let input: InteractionInput = "hi".into();
724 let json = serde_json::to_string(&input).unwrap();
725 assert_eq!(json, "\"hi\"");
726 }
727
728 #[test]
729 fn create_interaction_config_new_sets_fields() {
730 let config = CreateInteractionConfig::new("model-1", "hello");
731 assert_eq!(config.model.as_deref(), Some("model-1"));
732 match config.input {
733 InteractionInput::Text(value) => assert_eq!(value, "hello"),
734 _ => panic!("expected text input"),
735 }
736 }
737
738 #[test]
739 fn interaction_event_alias_event_type() {
740 let json = r#"{
741 "eventType": "interaction.complete",
742 "eventId": "evt_9",
743 "interaction": { "id": "int_456", "status": "completed" }
744 }"#;
745 let event: InteractionSseEvent = serde_json::from_str(json).unwrap();
746 assert_eq!(event.event_type.as_deref(), Some("interaction.complete"));
747 assert_eq!(event.event_id.as_deref(), Some("evt_9"));
748 assert_eq!(
749 event.interaction.as_ref().and_then(|d| d.id.as_deref()),
750 Some("int_456")
751 );
752 }
753}