1use serde::de::{self, Visitor};
4use serde::ser::SerializeMap;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use std::fmt;
7
8use crate::content::Content;
9use crate::tools::{FunctionCallingMode, Tool};
10
11#[derive(Clone, Debug, PartialEq)]
32#[non_exhaustive]
33pub enum Role {
34 User,
36 Model,
38 Unknown {
40 role_type: String,
42 data: serde_json::Value,
44 },
45}
46
47impl Role {
48 #[must_use]
50 pub const fn is_unknown(&self) -> bool {
51 matches!(self, Self::Unknown { .. })
52 }
53
54 #[must_use]
56 pub fn unknown_role_type(&self) -> Option<&str> {
57 match self {
58 Self::Unknown { role_type, .. } => Some(role_type),
59 _ => None,
60 }
61 }
62
63 #[must_use]
65 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
66 match self {
67 Self::Unknown { data, .. } => Some(data),
68 _ => None,
69 }
70 }
71}
72
73impl fmt::Display for Role {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Self::User => write!(f, "user"),
77 Self::Model => write!(f, "model"),
78 Self::Unknown { role_type, .. } => write!(f, "{}", role_type),
79 }
80 }
81}
82
83impl Serialize for Role {
84 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85 where
86 S: Serializer,
87 {
88 match self {
89 Role::User => serializer.serialize_str("user"),
90 Role::Model => serializer.serialize_str("model"),
91 Role::Unknown { role_type, .. } => serializer.serialize_str(role_type),
92 }
93 }
94}
95
96impl<'de> Deserialize<'de> for Role {
97 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98 where
99 D: Deserializer<'de>,
100 {
101 let s = String::deserialize(deserializer)?;
102 match s.as_str() {
103 "user" => Ok(Role::User),
104 "model" => Ok(Role::Model),
105 other => {
106 tracing::warn!(
107 "Encountered unknown Role '{}' - using Unknown variant (Evergreen)",
108 other
109 );
110 Ok(Role::Unknown {
111 role_type: other.to_string(),
112 data: serde_json::Value::String(other.to_string()),
113 })
114 }
115 }
116 }
117}
118
119#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
140#[serde(untagged)]
141#[non_exhaustive]
142pub enum TurnContent {
143 Text(String),
145 Parts(Vec<Content>),
147}
148
149impl From<String> for TurnContent {
150 fn from(s: String) -> Self {
151 Self::Text(s)
152 }
153}
154
155impl From<&str> for TurnContent {
156 fn from(s: &str) -> Self {
157 Self::Text(s.to_string())
158 }
159}
160
161impl From<Vec<Content>> for TurnContent {
162 fn from(parts: Vec<Content>) -> Self {
163 Self::Parts(parts)
164 }
165}
166
167impl TurnContent {
168 #[must_use]
170 pub fn as_text(&self) -> Option<&str> {
171 match self {
172 Self::Text(t) => Some(t),
173 Self::Parts(_) => None,
174 }
175 }
176
177 #[must_use]
179 pub fn as_parts(&self) -> Option<&[Content]> {
180 match self {
181 Self::Parts(p) => Some(p),
182 Self::Text(_) => None,
183 }
184 }
185
186 #[must_use]
188 pub const fn is_text(&self) -> bool {
189 matches!(self, Self::Text(_))
190 }
191
192 #[must_use]
194 pub const fn is_parts(&self) -> bool {
195 matches!(self, Self::Parts(_))
196 }
197}
198
199#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
223pub struct Turn {
224 role: Role,
225 content: TurnContent,
226}
227
228impl Turn {
229 pub fn new(role: Role, content: impl Into<TurnContent>) -> Self {
231 Self {
232 role,
233 content: content.into(),
234 }
235 }
236
237 #[must_use]
239 pub fn role(&self) -> &Role {
240 &self.role
241 }
242
243 #[must_use]
245 pub fn content(&self) -> &TurnContent {
246 &self.content
247 }
248
249 pub fn user(content: impl Into<TurnContent>) -> Self {
259 Self::new(Role::User, content)
260 }
261
262 pub fn model(content: impl Into<TurnContent>) -> Self {
272 Self::new(Role::Model, content)
273 }
274
275 #[must_use]
277 pub fn is_user(&self) -> bool {
278 *self.role() == Role::User
279 }
280
281 #[must_use]
283 pub fn is_model(&self) -> bool {
284 *self.role() == Role::Model
285 }
286
287 #[must_use]
289 pub fn as_text(&self) -> Option<&str> {
290 self.content().as_text()
291 }
292}
293
294#[derive(Clone, Serialize, Deserialize, Debug)]
322#[serde(untagged)]
323#[non_exhaustive]
324pub enum InteractionInput {
325 Text(String),
327 Content(Vec<Content>),
329 Turns(Vec<Turn>),
331}
332
333#[derive(Clone, Debug, PartialEq, Eq)]
346#[non_exhaustive]
347pub enum ThinkingLevel {
348 Minimal,
350 Low,
352 Medium,
354 High,
356 Unknown {
358 level_type: String,
360 data: serde_json::Value,
362 },
363}
364
365impl ThinkingLevel {
366 #[must_use]
368 pub const fn is_unknown(&self) -> bool {
369 matches!(self, Self::Unknown { .. })
370 }
371
372 #[must_use]
374 pub fn unknown_level_type(&self) -> Option<&str> {
375 match self {
376 Self::Unknown { level_type, .. } => Some(level_type),
377 _ => None,
378 }
379 }
380
381 #[must_use]
383 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
384 match self {
385 Self::Unknown { data, .. } => Some(data),
386 _ => None,
387 }
388 }
389}
390
391impl Serialize for ThinkingLevel {
392 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
393 where
394 S: Serializer,
395 {
396 match self {
397 ThinkingLevel::Minimal => serializer.serialize_str("minimal"),
398 ThinkingLevel::Low => serializer.serialize_str("low"),
399 ThinkingLevel::Medium => serializer.serialize_str("medium"),
400 ThinkingLevel::High => serializer.serialize_str("high"),
401 ThinkingLevel::Unknown { level_type, data } => {
402 if data.is_string() || data.is_null() {
404 serializer.serialize_str(level_type)
405 } else {
406 let mut map = serializer.serialize_map(None)?;
408 map.serialize_entry("level", level_type)?;
409 map.serialize_entry("data", data)?;
410 map.end()
411 }
412 }
413 }
414 }
415}
416
417impl<'de> Deserialize<'de> for ThinkingLevel {
418 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
419 where
420 D: Deserializer<'de>,
421 {
422 deserializer.deserialize_any(ThinkingLevelVisitor)
423 }
424}
425
426struct ThinkingLevelVisitor;
427
428impl<'de> Visitor<'de> for ThinkingLevelVisitor {
429 type Value = ThinkingLevel;
430
431 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
432 formatter.write_str("a thinking level string or object")
433 }
434
435 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
436 where
437 E: de::Error,
438 {
439 match value {
440 "minimal" => Ok(ThinkingLevel::Minimal),
441 "low" => Ok(ThinkingLevel::Low),
442 "medium" => Ok(ThinkingLevel::Medium),
443 "high" => Ok(ThinkingLevel::High),
444 other => {
445 tracing::warn!(
446 "Encountered unknown ThinkingLevel '{}' - using Unknown variant (Evergreen)",
447 other
448 );
449 Ok(ThinkingLevel::Unknown {
450 level_type: other.to_string(),
451 data: serde_json::Value::String(other.to_string()),
452 })
453 }
454 }
455 }
456
457 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
458 where
459 A: de::MapAccess<'de>,
460 {
461 let value: serde_json::Value =
463 Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
464 let level_type = value
465 .get("level")
466 .and_then(|v| v.as_str())
467 .unwrap_or("unknown")
468 .to_string();
469
470 tracing::warn!(
471 "Encountered unknown ThinkingLevel object '{}' - using Unknown variant (Evergreen)",
472 level_type
473 );
474 Ok(ThinkingLevel::Unknown {
475 level_type,
476 data: value,
477 })
478 }
479}
480
481#[derive(Clone, Serialize, Deserialize, Debug, Default)]
483#[serde(rename_all = "camelCase")]
484pub struct GenerationConfig {
485 #[serde(skip_serializing_if = "Option::is_none")]
486 pub temperature: Option<f32>,
487 #[serde(skip_serializing_if = "Option::is_none")]
488 pub max_output_tokens: Option<i32>,
489 #[serde(skip_serializing_if = "Option::is_none")]
490 pub top_p: Option<f32>,
491 #[serde(skip_serializing_if = "Option::is_none")]
492 pub top_k: Option<i32>,
493 #[serde(skip_serializing_if = "Option::is_none")]
495 pub thinking_level: Option<ThinkingLevel>,
496 #[serde(skip_serializing_if = "Option::is_none")]
501 pub seed: Option<i64>,
502 #[serde(skip_serializing_if = "Option::is_none")]
506 pub stop_sequences: Option<Vec<String>>,
507 #[serde(skip_serializing_if = "Option::is_none")]
511 pub thinking_summaries: Option<ThinkingSummaries>,
512 #[serde(skip_serializing_if = "Option::is_none")]
520 pub tool_choice: Option<FunctionCallingMode>,
521 #[serde(skip_serializing_if = "Option::is_none")]
525 pub speech_config: Option<SpeechConfig>,
526}
527
528#[derive(Clone, Serialize, Deserialize, Debug, Default)]
550#[serde(rename_all = "camelCase")]
551pub struct SpeechConfig {
552 #[serde(skip_serializing_if = "Option::is_none")]
556 pub voice: Option<String>,
557
558 #[serde(skip_serializing_if = "Option::is_none")]
562 pub language: Option<String>,
563
564 #[serde(skip_serializing_if = "Option::is_none")]
569 pub speaker: Option<String>,
570}
571
572impl SpeechConfig {
573 #[must_use]
575 pub fn with_voice(voice: impl Into<String>) -> Self {
576 Self {
577 voice: Some(voice.into()),
578 ..Default::default()
579 }
580 }
581
582 #[must_use]
584 pub fn with_voice_and_language(voice: impl Into<String>, language: impl Into<String>) -> Self {
585 Self {
586 voice: Some(voice.into()),
587 language: Some(language.into()),
588 ..Default::default()
589 }
590 }
591}
592
593#[derive(Clone, Serialize, Deserialize, Debug)]
657#[serde(rename_all = "camelCase")]
658pub struct InteractionRequest {
659 #[serde(skip_serializing_if = "Option::is_none")]
661 pub model: Option<String>,
662
663 #[serde(skip_serializing_if = "Option::is_none")]
665 pub agent: Option<String>,
666
667 #[serde(rename = "agent_config", skip_serializing_if = "Option::is_none")]
669 pub agent_config: Option<AgentConfig>,
670
671 pub input: InteractionInput,
673
674 #[serde(skip_serializing_if = "Option::is_none")]
676 pub previous_interaction_id: Option<String>,
677
678 #[serde(skip_serializing_if = "Option::is_none")]
680 pub tools: Option<Vec<Tool>>,
681
682 #[serde(skip_serializing_if = "Option::is_none")]
684 pub response_modalities: Option<Vec<String>>,
685
686 #[serde(skip_serializing_if = "Option::is_none")]
688 pub response_format: Option<serde_json::Value>,
689
690 #[serde(skip_serializing_if = "Option::is_none")]
695 pub response_mime_type: Option<String>,
696
697 #[serde(skip_serializing_if = "Option::is_none")]
699 pub generation_config: Option<GenerationConfig>,
700
701 #[serde(skip_serializing_if = "Option::is_none")]
703 pub stream: Option<bool>,
704
705 #[serde(skip_serializing_if = "Option::is_none")]
707 pub background: Option<bool>,
708
709 #[serde(skip_serializing_if = "Option::is_none")]
711 pub store: Option<bool>,
712
713 #[serde(skip_serializing_if = "Option::is_none")]
715 pub system_instruction: Option<InteractionInput>,
716}
717
718#[derive(Clone, Debug, PartialEq, Eq)]
735#[non_exhaustive]
736pub enum ThinkingSummaries {
737 Auto,
739 None,
741 Unknown {
743 summaries_type: String,
745 data: serde_json::Value,
747 },
748}
749
750impl ThinkingSummaries {
751 #[must_use]
753 pub const fn is_unknown(&self) -> bool {
754 matches!(self, Self::Unknown { .. })
755 }
756
757 #[must_use]
759 pub fn unknown_summaries_type(&self) -> Option<&str> {
760 match self {
761 Self::Unknown { summaries_type, .. } => Some(summaries_type),
762 _ => None,
763 }
764 }
765
766 #[must_use]
768 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
769 match self {
770 Self::Unknown { data, .. } => Some(data),
771 _ => None,
772 }
773 }
774
775 #[must_use]
781 pub fn to_agent_config_value(&self) -> serde_json::Value {
782 match self {
783 ThinkingSummaries::Auto => {
784 serde_json::Value::String("THINKING_SUMMARIES_AUTO".to_string())
785 }
786 ThinkingSummaries::None => {
787 serde_json::Value::String("THINKING_SUMMARIES_NONE".to_string())
788 }
789 ThinkingSummaries::Unknown { summaries_type, .. } => {
790 serde_json::Value::String(summaries_type.clone())
792 }
793 }
794 }
795}
796
797impl Serialize for ThinkingSummaries {
798 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
799 where
800 S: Serializer,
801 {
802 match self {
805 ThinkingSummaries::Auto => serializer.serialize_str("auto"),
806 ThinkingSummaries::None => serializer.serialize_str("none"),
807 ThinkingSummaries::Unknown {
808 summaries_type,
809 data,
810 } => {
811 if data.is_string() || data.is_null() {
813 serializer.serialize_str(summaries_type)
814 } else {
815 let mut map = serializer.serialize_map(None)?;
817 map.serialize_entry("summaries", summaries_type)?;
818 map.serialize_entry("data", data)?;
819 map.end()
820 }
821 }
822 }
823 }
824}
825
826impl<'de> Deserialize<'de> for ThinkingSummaries {
827 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
828 where
829 D: Deserializer<'de>,
830 {
831 deserializer.deserialize_any(ThinkingSummariesVisitor)
832 }
833}
834
835struct ThinkingSummariesVisitor;
836
837impl<'de> Visitor<'de> for ThinkingSummariesVisitor {
838 type Value = ThinkingSummaries;
839
840 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
841 formatter.write_str("a thinking summaries string or object")
842 }
843
844 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
845 where
846 E: de::Error,
847 {
848 match value {
849 "THINKING_SUMMARIES_AUTO" | "auto" => Ok(ThinkingSummaries::Auto),
851 "THINKING_SUMMARIES_NONE" | "none" => Ok(ThinkingSummaries::None),
852 other => {
853 tracing::warn!(
854 "Encountered unknown ThinkingSummaries '{}' - using Unknown variant (Evergreen)",
855 other
856 );
857 Ok(ThinkingSummaries::Unknown {
858 summaries_type: other.to_string(),
859 data: serde_json::Value::String(other.to_string()),
860 })
861 }
862 }
863 }
864
865 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
866 where
867 A: de::MapAccess<'de>,
868 {
869 let value: serde_json::Value =
871 Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
872 let summaries_type = value
873 .get("summaries")
874 .and_then(|v| v.as_str())
875 .unwrap_or("unknown")
876 .to_string();
877
878 tracing::warn!(
879 "Encountered unknown ThinkingSummaries object '{}' - using Unknown variant (Evergreen)",
880 summaries_type
881 );
882 Ok(ThinkingSummaries::Unknown {
883 summaries_type,
884 data: value,
885 })
886 }
887}
888
889#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
916#[serde(transparent)]
917pub struct AgentConfig(serde_json::Value);
918
919impl AgentConfig {
920 #[must_use]
924 pub fn from_value(value: serde_json::Value) -> Self {
925 Self(value)
926 }
927
928 #[must_use]
930 pub fn as_value(&self) -> &serde_json::Value {
931 &self.0
932 }
933
934 #[must_use]
936 pub fn config_type(&self) -> Option<&str> {
937 self.0.get("type").and_then(|v| v.as_str())
938 }
939}
940
941#[derive(Clone, Debug, Default)]
956pub struct DeepResearchConfig {
957 thinking_summaries: Option<ThinkingSummaries>,
958}
959
960impl DeepResearchConfig {
961 #[must_use]
963 pub fn new() -> Self {
964 Self::default()
965 }
966
967 #[must_use]
971 pub fn with_thinking_summaries(mut self, summaries: ThinkingSummaries) -> Self {
972 self.thinking_summaries = Some(summaries);
973 self
974 }
975}
976
977impl From<DeepResearchConfig> for AgentConfig {
978 fn from(config: DeepResearchConfig) -> Self {
979 let mut map = serde_json::Map::new();
980 map.insert(
981 "type".into(),
982 serde_json::Value::String("deep-research".into()),
983 );
984 if let Some(ts) = config.thinking_summaries {
985 map.insert("thinking_summaries".into(), ts.to_agent_config_value());
987 }
988 AgentConfig(serde_json::Value::Object(map))
989 }
990}
991
992#[derive(Clone, Debug, Default)]
1005pub struct DynamicConfig;
1006
1007impl DynamicConfig {
1008 #[must_use]
1010 pub fn new() -> Self {
1011 Self
1012 }
1013}
1014
1015impl From<DynamicConfig> for AgentConfig {
1016 fn from(_: DynamicConfig) -> Self {
1017 AgentConfig(serde_json::json!({"type": "dynamic"}))
1018 }
1019}
1020
1021#[cfg(test)]
1022mod tests {
1023 use super::*;
1024
1025 #[test]
1030 fn test_thinking_summaries_serialization() {
1031 assert_eq!(
1033 serde_json::to_string(&ThinkingSummaries::Auto).unwrap(),
1034 "\"auto\""
1035 );
1036
1037 assert_eq!(
1038 serde_json::to_string(&ThinkingSummaries::None).unwrap(),
1039 "\"none\""
1040 );
1041 }
1042
1043 #[test]
1044 fn test_thinking_summaries_agent_config_format() {
1045 assert_eq!(
1047 ThinkingSummaries::Auto.to_agent_config_value(),
1048 serde_json::Value::String("THINKING_SUMMARIES_AUTO".to_string())
1049 );
1050
1051 assert_eq!(
1052 ThinkingSummaries::None.to_agent_config_value(),
1053 serde_json::Value::String("THINKING_SUMMARIES_NONE".to_string())
1054 );
1055 }
1056
1057 #[test]
1058 fn test_thinking_summaries_deserialization() {
1059 assert_eq!(
1061 serde_json::from_str::<ThinkingSummaries>("\"THINKING_SUMMARIES_AUTO\"").unwrap(),
1062 ThinkingSummaries::Auto
1063 );
1064 assert_eq!(
1065 serde_json::from_str::<ThinkingSummaries>("\"THINKING_SUMMARIES_NONE\"").unwrap(),
1066 ThinkingSummaries::None
1067 );
1068
1069 assert_eq!(
1071 serde_json::from_str::<ThinkingSummaries>("\"auto\"").unwrap(),
1072 ThinkingSummaries::Auto
1073 );
1074 assert_eq!(
1075 serde_json::from_str::<ThinkingSummaries>("\"none\"").unwrap(),
1076 ThinkingSummaries::None
1077 );
1078 }
1079
1080 #[test]
1081 fn test_thinking_summaries_unknown_roundtrip() {
1082 let unknown: ThinkingSummaries = serde_json::from_str("\"future_variant\"").unwrap();
1083 assert!(unknown.is_unknown());
1084 assert_eq!(unknown.unknown_summaries_type(), Some("future_variant"));
1085
1086 let json = serde_json::to_string(&unknown).unwrap();
1088 assert_eq!(json, "\"future_variant\"");
1089 }
1090
1091 #[test]
1092 fn test_deep_research_config_serialization() {
1093 let config: AgentConfig = DeepResearchConfig::new()
1094 .with_thinking_summaries(ThinkingSummaries::Auto)
1095 .into();
1096
1097 let json = serde_json::to_string(&config).expect("Serialization failed");
1098 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1099
1100 assert_eq!(value["type"], "deep-research");
1101 assert_eq!(value["thinking_summaries"], "THINKING_SUMMARIES_AUTO");
1102 }
1103
1104 #[test]
1105 fn test_deep_research_config_without_thinking_summaries() {
1106 let config: AgentConfig = DeepResearchConfig::new().into();
1107
1108 let json = serde_json::to_string(&config).expect("Serialization failed");
1109 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1110
1111 assert_eq!(value["type"], "deep-research");
1112 assert!(value.get("thinking_summaries").is_none());
1113 }
1114
1115 #[test]
1116 fn test_dynamic_config_serialization() {
1117 let config: AgentConfig = DynamicConfig::new().into();
1118
1119 let json = serde_json::to_string(&config).expect("Serialization failed");
1120 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1121
1122 assert_eq!(value["type"], "dynamic");
1123 }
1124
1125 #[test]
1126 fn test_agent_config_from_raw_json() {
1127 let config = AgentConfig::from_value(serde_json::json!({
1128 "type": "custom-agent",
1129 "option1": true,
1130 "option2": "value"
1131 }));
1132
1133 assert_eq!(config.config_type(), Some("custom-agent"));
1134 assert_eq!(config.as_value()["option1"], true);
1135 }
1136
1137 #[test]
1138 fn test_agent_config_roundtrip() {
1139 let config: AgentConfig = DeepResearchConfig::new()
1140 .with_thinking_summaries(ThinkingSummaries::Auto)
1141 .into();
1142
1143 let json = serde_json::to_string(&config).expect("Serialization failed");
1144 let parsed: AgentConfig = serde_json::from_str(&json).expect("Deserialization failed");
1145
1146 assert_eq!(config, parsed);
1147 }
1148
1149 #[test]
1154 fn test_speech_config_with_voice() {
1155 let config = SpeechConfig::with_voice("Kore");
1156 assert_eq!(config.voice, Some("Kore".to_string()));
1157 assert_eq!(config.language, None);
1158 assert_eq!(config.speaker, None);
1159 }
1160
1161 #[test]
1162 fn test_speech_config_with_voice_and_language() {
1163 let config = SpeechConfig::with_voice_and_language("Puck", "en-GB");
1164 assert_eq!(config.voice, Some("Puck".to_string()));
1165 assert_eq!(config.language, Some("en-GB".to_string()));
1166 assert_eq!(config.speaker, None);
1167 }
1168
1169 #[test]
1170 fn test_speech_config_serialization() {
1171 let config = SpeechConfig {
1172 voice: Some("Fenrir".to_string()),
1173 language: Some("en-US".to_string()),
1174 speaker: None,
1175 };
1176
1177 let json = serde_json::to_string(&config).expect("Serialization failed");
1178 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1179
1180 assert_eq!(value["voice"], "Fenrir");
1182 assert_eq!(value["language"], "en-US");
1183 assert!(value.get("speaker").is_none()); assert!(
1189 value.get("voiceConfig").is_none(),
1190 "Should use flat format, not nested voiceConfig"
1191 );
1192 assert!(
1193 value.get("prebuiltVoiceConfig").is_none(),
1194 "Should use flat format, not nested prebuiltVoiceConfig"
1195 );
1196 }
1197
1198 #[test]
1199 fn test_speech_config_roundtrip() {
1200 let config = SpeechConfig {
1201 voice: Some("Aoede".to_string()),
1202 language: Some("es-ES".to_string()),
1203 speaker: Some("narrator".to_string()),
1204 };
1205
1206 let json = serde_json::to_string(&config).expect("Serialization failed");
1207 let parsed: SpeechConfig = serde_json::from_str(&json).expect("Deserialization failed");
1208
1209 assert_eq!(config.voice, parsed.voice);
1210 assert_eq!(config.language, parsed.language);
1211 assert_eq!(config.speaker, parsed.speaker);
1212 }
1213
1214 #[test]
1215 fn test_speech_config_default() {
1216 let config = SpeechConfig::default();
1217 assert_eq!(config.voice, None);
1218 assert_eq!(config.language, None);
1219 assert_eq!(config.speaker, None);
1220 }
1221}