use std::collections::BTreeMap;
use ferrin_provider_util::provider_options::parse_provider_options;
use ferrin_spec::JsonObject;
use ferrin_spec::JsonValue;
use ferrin_spec::ProviderOptions;
use ferrin_spec::error::InvalidArgumentError;
use serde::Deserialize;
use serde::Serialize;
use crate::config::CANONICAL_OPTIONS_KEY;
use crate::config::GoogleConfig;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThinkingConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thinking_budget: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_thoughts: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thinking_level: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SafetySetting {
pub category: String,
pub threshold: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImageConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub person_generation: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prominent_people: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image_output_options: Option<JsonObject>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GoogleLanguageModelOptions {
#[serde(default)]
pub response_modalities: Option<Vec<String>>,
#[serde(default)]
pub thinking_config: Option<ThinkingConfig>,
#[serde(default)]
pub cached_content: Option<String>,
#[serde(default)]
pub structured_outputs: Option<bool>,
#[serde(default)]
pub safety_settings: Option<Vec<SafetySetting>>,
#[serde(default)]
pub threshold: Option<String>,
#[serde(default)]
pub audio_timestamp: Option<bool>,
#[serde(default)]
pub labels: Option<BTreeMap<String, String>>,
#[serde(default)]
pub media_resolution: Option<String>,
#[serde(default)]
pub image_config: Option<ImageConfig>,
#[serde(default)]
pub retrieval_config: Option<JsonObject>,
#[serde(default)]
pub stream_function_call_arguments: Option<bool>,
#[serde(default)]
pub service_tier: Option<String>,
#[serde(default)]
pub shared_request_type: Option<String>,
#[serde(default)]
pub request_type: Option<String>,
}
impl GoogleLanguageModelOptions {
fn merge(mut self, other: Self) -> Self {
macro_rules! take {
($($field:ident),* $(,)?) => {
$( if other.$field.is_some() { self.$field = other.$field; } )*
};
}
take!(
response_modalities,
thinking_config,
cached_content,
structured_outputs,
safety_settings,
threshold,
audio_timestamp,
labels,
media_resolution,
image_config,
retrieval_config,
stream_function_call_arguments,
service_tier,
shared_request_type,
request_type,
);
self
}
}
pub fn parse_merged<T: serde::de::DeserializeOwned + Default>(
config: &GoogleConfig,
provider_options: &ProviderOptions,
merge: impl FnOnce(T, T) -> T,
) -> Result<T, InvalidArgumentError> {
let canonical =
parse_provider_options::<T>(CANONICAL_OPTIONS_KEY, provider_options)?.unwrap_or_default();
let key = config.options_key();
if key == CANONICAL_OPTIONS_KEY {
return Ok(canonical);
}
match parse_provider_options::<T>(key, provider_options)? {
Some(custom) => Ok(merge(canonical, custom)),
None => Ok(canonical),
}
}
pub fn parse_options(
config: &GoogleConfig,
provider_options: &ProviderOptions,
) -> Result<GoogleLanguageModelOptions, InvalidArgumentError> {
parse_merged(config, provider_options, GoogleLanguageModelOptions::merge)
}
#[must_use]
pub fn part_options<'a>(
config: &GoogleConfig,
provider_options: Option<&'a ProviderOptions>,
) -> Option<&'a JsonObject> {
let options = provider_options?;
options
.get(config.options_key())
.or_else(|| options.get(CANONICAL_OPTIONS_KEY))
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PartOptions {
#[serde(default)]
pub thought_signature: Option<String>,
#[serde(default)]
pub thought: Option<bool>,
#[serde(default)]
pub server_tool_call_id: Option<String>,
#[serde(default)]
pub server_tool_type: Option<String>,
}
#[must_use]
pub fn read_part_options(
config: &GoogleConfig,
provider_options: Option<&ProviderOptions>,
) -> PartOptions {
read_options(part_options(config, provider_options)).unwrap_or_default()
}
#[must_use]
pub fn read_options<T: serde::de::DeserializeOwned>(object: Option<&JsonObject>) -> Option<T> {
let object = object?;
serde_json::from_value(JsonValue::Object(object.clone())).ok()
}