1use std::collections::BTreeMap;
5
6use ferrin_provider_util::provider_options::parse_provider_options;
7use ferrin_spec::JsonObject;
8use ferrin_spec::JsonValue;
9use ferrin_spec::ProviderOptions;
10use ferrin_spec::error::InvalidArgumentError;
11use serde::Deserialize;
12use serde::Serialize;
13
14use crate::config::CANONICAL_OPTIONS_KEY;
15use crate::config::GoogleConfig;
16
17#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct ThinkingConfig {
21 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub thinking_budget: Option<i64>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub include_thoughts: Option<bool>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub thinking_level: Option<String>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct SafetySetting {
35 pub category: String,
37 pub threshold: String,
39}
40
41#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct ImageConfig {
45 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub aspect_ratio: Option<String>,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub image_size: Option<String>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub person_generation: Option<String>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub prominent_people: Option<String>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub image_output_options: Option<JsonObject>,
60}
61
62#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
64#[serde(rename_all = "camelCase", deny_unknown_fields)]
65pub struct GoogleLanguageModelOptions {
66 #[serde(default)]
68 pub response_modalities: Option<Vec<String>>,
69 #[serde(default)]
71 pub thinking_config: Option<ThinkingConfig>,
72 #[serde(default)]
74 pub cached_content: Option<String>,
75 #[serde(default)]
77 pub structured_outputs: Option<bool>,
78 #[serde(default)]
80 pub safety_settings: Option<Vec<SafetySetting>>,
81 #[serde(default)]
83 pub threshold: Option<String>,
84 #[serde(default)]
86 pub audio_timestamp: Option<bool>,
87 #[serde(default)]
89 pub labels: Option<BTreeMap<String, String>>,
90 #[serde(default)]
92 pub media_resolution: Option<String>,
93 #[serde(default)]
95 pub image_config: Option<ImageConfig>,
96 #[serde(default)]
99 pub retrieval_config: Option<JsonObject>,
100 #[serde(default)]
102 pub stream_function_call_arguments: Option<bool>,
103 #[serde(default)]
105 pub service_tier: Option<String>,
106 #[serde(default)]
108 pub shared_request_type: Option<String>,
109 #[serde(default)]
111 pub request_type: Option<String>,
112}
113
114impl GoogleLanguageModelOptions {
115 fn merge(mut self, other: Self) -> Self {
117 macro_rules! take {
118 ($($field:ident),* $(,)?) => {
119 $( if other.$field.is_some() { self.$field = other.$field; } )*
120 };
121 }
122 take!(
123 response_modalities,
124 thinking_config,
125 cached_content,
126 structured_outputs,
127 safety_settings,
128 threshold,
129 audio_timestamp,
130 labels,
131 media_resolution,
132 image_config,
133 retrieval_config,
134 stream_function_call_arguments,
135 service_tier,
136 shared_request_type,
137 request_type,
138 );
139 self
140 }
141}
142
143pub fn parse_merged<T: serde::de::DeserializeOwned + Default>(
152 config: &GoogleConfig,
153 provider_options: &ProviderOptions,
154 merge: impl FnOnce(T, T) -> T,
155) -> Result<T, InvalidArgumentError> {
156 let canonical =
157 parse_provider_options::<T>(CANONICAL_OPTIONS_KEY, provider_options)?.unwrap_or_default();
158 let key = config.options_key();
159 if key == CANONICAL_OPTIONS_KEY {
160 return Ok(canonical);
161 }
162 match parse_provider_options::<T>(key, provider_options)? {
163 Some(custom) => Ok(merge(canonical, custom)),
164 None => Ok(canonical),
165 }
166}
167
168pub fn parse_options(
175 config: &GoogleConfig,
176 provider_options: &ProviderOptions,
177) -> Result<GoogleLanguageModelOptions, InvalidArgumentError> {
178 parse_merged(config, provider_options, GoogleLanguageModelOptions::merge)
179}
180
181#[must_use]
184pub fn part_options<'a>(
185 config: &GoogleConfig,
186 provider_options: Option<&'a ProviderOptions>,
187) -> Option<&'a JsonObject> {
188 let options = provider_options?;
189 options
190 .get(config.options_key())
191 .or_else(|| options.get(CANONICAL_OPTIONS_KEY))
192}
193
194#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct PartOptions {
199 #[serde(default)]
201 pub thought_signature: Option<String>,
202 #[serde(default)]
204 pub thought: Option<bool>,
205 #[serde(default)]
207 pub server_tool_call_id: Option<String>,
208 #[serde(default)]
210 pub server_tool_type: Option<String>,
211}
212
213#[must_use]
215pub fn read_part_options(
216 config: &GoogleConfig,
217 provider_options: Option<&ProviderOptions>,
218) -> PartOptions {
219 read_options(part_options(config, provider_options)).unwrap_or_default()
220}
221
222#[must_use]
224pub fn read_options<T: serde::de::DeserializeOwned>(object: Option<&JsonObject>) -> Option<T> {
225 let object = object?;
226 serde_json::from_value(JsonValue::Object(object.clone())).ok()
227}