use serde::{Deserialize, Serialize};
use crate::config::{GenerationConfig, SpeechConfig, ThinkingConfig};
use crate::content::{Blob, Content, FunctionCall, FunctionResponse};
use crate::enums::{
ActivityHandling, EndSensitivity, MediaResolution, Modality, StartSensitivity,
TurnCompleteReason, TurnCoverage, VadSignalType,
};
use crate::grounding::GroundingMetadata;
use crate::http::HttpOptions;
use crate::response::{UrlContextMetadata, UsageMetadata};
use crate::tool::Tool;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveClientSetup {
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generation_config: Option<GenerationConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_instruction: Option<Content>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub realtime_input_config: Option<RealtimeInputConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_resumption: Option<SessionResumptionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_window_compression: Option<ContextWindowCompressionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_audio_transcription: Option<AudioTranscriptionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_audio_transcription: Option<AudioTranscriptionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub proactivity: Option<ProactivityConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub explicit_vad_signal: Option<bool>,
}
pub type LiveSetup = LiveClientSetup;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveServerSetupComplete {
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transcription {
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub finished: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveServerContent {
#[serde(skip_serializing_if = "Option::is_none")]
pub model_turn: Option<Content>,
#[serde(skip_serializing_if = "Option::is_none")]
pub turn_complete: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interrupted: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub grounding_metadata: Option<GroundingMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generation_complete: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_transcription: Option<Transcription>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_transcription: Option<Transcription>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url_context_metadata: Option<UrlContextMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub turn_complete_reason: Option<TurnCompleteReason>,
#[serde(skip_serializing_if = "Option::is_none")]
pub waiting_for_input: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveServerToolCall {
#[serde(skip_serializing_if = "Option::is_none")]
pub function_calls: Option<Vec<FunctionCall>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveServerToolCallCancellation {
#[serde(skip_serializing_if = "Option::is_none")]
pub ids: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveServerGoAway {
#[serde(skip_serializing_if = "Option::is_none")]
pub time_left: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveServerSessionResumptionUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub new_handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resumable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_consumed_client_message_index: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VoiceActivityDetectionSignal {
#[serde(skip_serializing_if = "Option::is_none")]
pub vad_signal_type: Option<VadSignalType>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveServerMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub setup_complete: Option<LiveServerSetupComplete>,
#[serde(skip_serializing_if = "Option::is_none")]
pub server_content: Option<LiveServerContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call: Option<LiveServerToolCall>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_cancellation: Option<LiveServerToolCallCancellation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage_metadata: Option<UsageMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub go_away: Option<LiveServerGoAway>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_resumption_update: Option<LiveServerSessionResumptionUpdate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub voice_activity_detection_signal: Option<VoiceActivityDetectionSignal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AutomaticActivityDetection {
#[serde(skip_serializing_if = "Option::is_none")]
pub disabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_of_speech_sensitivity: Option<StartSensitivity>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_of_speech_sensitivity: Option<EndSensitivity>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prefix_padding_ms: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub silence_duration_ms: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RealtimeInputConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub automatic_activity_detection: Option<AutomaticActivityDetection>,
#[serde(skip_serializing_if = "Option::is_none")]
pub activity_handling: Option<ActivityHandling>,
#[serde(skip_serializing_if = "Option::is_none")]
pub turn_coverage: Option<TurnCoverage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionResumptionConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transparent: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContextWindowCompressionConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub trigger_tokens: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sliding_window: Option<SlidingWindow>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SlidingWindow {
#[serde(skip_serializing_if = "Option::is_none")]
pub target_tokens: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct AudioTranscriptionConfig {}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProactivityConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub proactive_audio: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveClientContent {
#[serde(skip_serializing_if = "Option::is_none")]
pub turns: Option<Vec<Content>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub turn_complete: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ActivityStart {}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ActivityEnd {}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveClientRealtimeInput {
#[serde(skip_serializing_if = "Option::is_none")]
pub media_chunks: Option<Vec<Blob>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio: Option<Blob>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio_stream_end: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video: Option<Blob>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub activity_start: Option<ActivityStart>,
#[serde(skip_serializing_if = "Option::is_none")]
pub activity_end: Option<ActivityEnd>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveClientToolResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub function_responses: Option<Vec<FunctionResponse>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveSendRealtimeInputParameters {
#[serde(skip_serializing_if = "Option::is_none")]
pub media: Option<Blob>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio: Option<Blob>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio_stream_end: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video: Option<Blob>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub activity_start: Option<ActivityStart>,
#[serde(skip_serializing_if = "Option::is_none")]
pub activity_end: Option<ActivityEnd>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveClientMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub setup: Option<LiveClientSetup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_content: Option<LiveClientContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub realtime_input: Option<LiveClientRealtimeInput>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_response: Option<LiveClientToolResponse>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct LiveConnectConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub http_options: Option<HttpOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generation_config: Option<GenerationConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_modalities: Option<Vec<Modality>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_k: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub media_resolution: Option<MediaResolution>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub speech_config: Option<SpeechConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thinking_config: Option<ThinkingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_affective_dialog: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_instruction: Option<Content>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_resumption: Option<SessionResumptionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_audio_transcription: Option<AudioTranscriptionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_audio_transcription: Option<AudioTranscriptionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub realtime_input_config: Option<RealtimeInputConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_window_compression: Option<ContextWindowCompressionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub proactivity: Option<ProactivityConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub explicit_vad_signal: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveSendClientContentParameters {
#[serde(skip_serializing_if = "Option::is_none")]
pub turns: Option<Vec<Content>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub turn_complete: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveSendToolResponseParameters {
#[serde(skip_serializing_if = "Option::is_none")]
pub function_responses: Option<Vec<FunctionResponse>>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::content::{Content, Part, Role};
use crate::enums::{ActivityHandling, StartSensitivity, VadSignalType};
#[test]
fn live_server_message_roundtrip() {
let message = LiveServerMessage {
setup_complete: Some(LiveServerSetupComplete {
session_id: Some("session-123".to_string()),
}),
server_content: Some(LiveServerContent {
model_turn: Some(Content::from_parts(vec![Part::text("hi")], Role::Model)),
turn_complete: Some(true),
interrupted: None,
grounding_metadata: None,
generation_complete: Some(true),
input_transcription: Some(Transcription {
text: Some("hello".to_string()),
finished: Some(true),
}),
output_transcription: None,
url_context_metadata: None,
turn_complete_reason: Some(TurnCompleteReason::NeedMoreInput),
waiting_for_input: Some(true),
}),
tool_call: None,
tool_call_cancellation: None,
usage_metadata: None,
go_away: Some(LiveServerGoAway {
time_left: Some("5s".to_string()),
}),
session_resumption_update: Some(LiveServerSessionResumptionUpdate {
new_handle: Some("handle-1".to_string()),
resumable: Some(true),
last_consumed_client_message_index: Some("42".to_string()),
}),
voice_activity_detection_signal: Some(VoiceActivityDetectionSignal {
vad_signal_type: Some(VadSignalType::VadSignalTypeSos),
}),
};
let json = serde_json::to_string(&message).unwrap();
let decoded: LiveServerMessage = serde_json::from_str(&json).unwrap();
let text = decoded
.server_content
.as_ref()
.and_then(|content| content.model_turn.as_ref())
.and_then(|content| content.first_text());
assert_eq!(text, Some("hi"));
}
#[test]
fn realtime_input_config_serializes() {
let config = RealtimeInputConfig {
automatic_activity_detection: Some(AutomaticActivityDetection {
disabled: Some(false),
start_of_speech_sensitivity: Some(StartSensitivity::StartSensitivityHigh),
end_of_speech_sensitivity: None,
prefix_padding_ms: Some(120),
silence_duration_ms: Some(300),
}),
activity_handling: Some(ActivityHandling::StartOfActivityInterrupts),
turn_coverage: None,
};
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("automaticActivityDetection"));
assert!(json.contains("startOfSpeechSensitivity"));
}
}