gproxy-protocol 4.1.14

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
use gproxy_protocol::gemini::live::*;
use serde::{Serialize, de::DeserializeOwned};
use serde_json::{Value, json};
fn round<T: Serialize + DeserializeOwned>(v: Value) -> T {
    let p: T = serde_json::from_value(v.clone()).unwrap();
    assert_eq!(serde_json::to_value(&p).unwrap(), v);
    p
}
fn setup() -> Value {
    json!({"model":"models/gemini-live","generationConfig":{"responseModalities":["AUDIO"],"temperature":0.5,"maxOutputTokens":128,"mediaResolution":"MEDIA_RESOLUTION_LOW","enableAffectiveDialog":true,"translationConfig":{"echoTargetLanguage":false,"targetLanguageCode":"es"},"audioTranscriptionConfig":{"languageCodes":["en"]},"speechConfig":{"languageCode":"en-US","voiceConfig":{"replicatedVoiceConfig":{"mimeType":"audio/wav","voiceSampleAudio":"AA==","consentAudio":"AQ==","voiceConsentSignature":{"signature":"sig"}}}}},"systemInstruction":{"parts":[{"text":"help"}]},"tools":[{"functionDeclarations":[{"name":"lookup","description":"lookup"}]}],"realtimeInputConfig":{"automaticActivityDetection":{"disabled":false,"startOfSpeechSensitivity":"START_SENSITIVITY_HIGH","prefixPaddingMs":0,"endOfSpeechSensitivity":"END_SENSITIVITY_LOW","silenceDurationMs":500},"activityHandling":"START_OF_ACTIVITY_INTERRUPTS","turnCoverage":"TURN_INCLUDES_AUDIO_ACTIVITY_AND_ALL_VIDEO"},"sessionResumption":{"handle":"handle"},"contextWindowCompression":{"triggerTokens":"1000","slidingWindow":{"targetTokens":500}},"inputAudioTranscription":{"languageCodes":["en"],"languageAuto":{},"languageHints":{"languageCodes":["en"]},"customVocabulary":["Gemini"],"adaptationPhrases":["gproxy"],"wordTimestamp":true,"diarization":true,"mode":"VERBATIM"},"outputAudioTranscription":{"mode":"SMART"},"proactivity":{"proactiveAudio":true},"historyConfig":{"initialHistoryInClientContent":true},"avatarConfig":{"avatarName":"a","customizedAvatar":{"imageMimeType":"image/jpeg","imageData":"AA=="},"audioBitrateBps":128000,"videoBitrateBps":1000000},"safetySettings":[{"category":"HARM_CATEGORY_HATE_SPEECH","threshold":"BLOCK_NONE"}]})
}
#[test]
fn setup_all_current_developer_fields_and_nested_audio_are_typed() {
    let p = round::<LiveClientMessage>(json!({"setup":setup()}));
    assert!(p.rest.is_empty());
    let s = p.setup.unwrap();
    assert!(s.rest.is_empty());
    let g = s.generation_config.unwrap();
    assert!(g.rest.is_empty());
    assert!(g.translation_config.unwrap().rest.is_empty());
    assert!(g.audio_transcription_config.unwrap().rest.is_empty());
    let speech = g.speech_config.unwrap();
    assert!(speech.rest.is_empty());
    let v = speech.voice_config.unwrap();
    assert!(v.rest.is_empty());
    let r = v.replicated_voice_config.unwrap();
    assert!(r.rest.is_empty());
    assert!(r.voice_consent_signature.unwrap().rest.is_empty());
    let rt = s.realtime_input_config.unwrap();
    assert!(rt.rest.is_empty());
    assert!(rt.automatic_activity_detection.unwrap().rest.is_empty());
    assert!(s.session_resumption.unwrap().rest.is_empty());
    let c = s.context_window_compression.unwrap();
    assert!(c.rest.is_empty());
    assert!(c.sliding_window.unwrap().rest.is_empty());
    assert!(s.proactivity.unwrap().rest.is_empty());
    assert!(s.history_config.unwrap().rest.is_empty());
    let avatar = s.avatar_config.unwrap();
    assert!(avatar.rest.is_empty());
    assert!(avatar.customized_avatar.unwrap().rest.is_empty());
    let t = s.input_audio_transcription.unwrap();
    assert!(t.rest.is_empty());
    assert!(t.language_auto.unwrap().rest.is_empty());
    assert!(t.language_hints.unwrap().rest.is_empty());
    assert!(s.output_audio_transcription.unwrap().rest.is_empty());
    assert!(serde_json::from_value::<BidiGenerateContentSetup>(json!({})).is_err());
}
#[test]
fn client_content_realtime_and_tool_responses_keep_sibling_keys() {
    let wire = json!({"clientContent":{"turns":[{"role":"user","parts":[{"text":"hello"}]}],"turnComplete":false},"realtimeInput":{"mediaChunks":[{"mimeType":"audio/pcm","data":"AA=="}],"audio":{"mimeType":"audio/pcm","data":"AQ=="},"audioStreamEnd":false,"video":{"mimeType":"image/jpeg","data":"Ag=="},"text":"hi","activityStart":{},"activityEnd":{},"mediaResolution":"MEDIA_RESOLUTION_LOW"},"toolResponse":{"functionResponses":[{"id":"call","name":"lookup","response":{"result":"ok"}}]}});
    let p = round::<LiveClientMessage>(wire);
    assert!(p.rest.is_empty());
    assert!(p.client_content.unwrap().rest.is_empty());
    let rt = p.realtime_input.unwrap();
    assert!(rt.rest.is_empty());
    assert!(rt.audio.unwrap().rest.is_empty());
    assert!(rt.video.unwrap().rest.is_empty());
    assert!(rt.activity_start.unwrap().rest.is_empty());
    assert!(rt.activity_end.unwrap().rest.is_empty());
    assert!(rt.media_chunks.unwrap()[0].rest.is_empty());
    assert!(p.tool_response.unwrap().rest.is_empty());
}
fn transcription() -> Value {
    json!({"text":"hello","finished":true,"languageCode":"en","speakerLabel":"A","words":[{"word":"hello","startOffset":"0s","endOffset":"0.5s"}]})
}
#[test]
fn all_server_siblings_usage_and_new_sdk_fields_are_typed() {
    let detail = json!({"modality":"AUDIO","tokenCount":1});
    let wire = json!({"setupComplete":{"sessionId":"session","voiceConsentSignature":{"signature":"s"}},"serverContent":{"modelTurn":{"role":"model","parts":[{"text":"hi"}]},"generationComplete":true,"turnComplete":true,"interrupted":false,"groundingMetadata":{"webSearchQueries":["x"],"searchEntryPoint":{"renderedContent":"html"}},"inputTranscription":transcription(),"outputTranscription":transcription(),"interimInputTranscription":transcription(),"urlContextMetadata":{"urlMetadata":[{"retrievedUrl":"https://x","urlRetrievalStatus":"URL_RETRIEVAL_STATUS_SUCCESS"}]},"waitingForInput":false,"turnCompleteReason":"NEED_MORE_INPUT","interactionStatus":"IDLE","speechState":0},"toolCall":{"functionCalls":[{"id":"call","name":"lookup","args":{"x":1}}]},"toolCallCancellation":{"ids":["call"]},"goAway":{"timeLeft":"30s"},"sessionResumptionUpdate":{"newHandle":"new","resumable":false,"lastConsumedClientMessageIndex":"12"},"usageMetadata":{"promptTokenCount":1,"cachedContentTokenCount":0,"responseTokenCount":2,"toolUsePromptTokenCount":0,"thoughtsTokenCount":1,"totalTokenCount":4,"promptTokensDetails":[detail.clone()],"cacheTokensDetails":[detail.clone()],"responseTokensDetails":[detail.clone()],"toolUsePromptTokensDetails":[detail],"serviceTier":"standard"},"voiceActivityDetectionSignal":{"vadSignalType":"VAD_SIGNAL_TYPE_SOS"},"voiceActivity":{"type":"ACTIVITY_END","audioOffset":"1s"}});
    let p = round::<LiveServerMessage>(wire);
    assert!(p.rest.is_empty());
    let setup = p.setup_complete.unwrap();
    assert!(setup.rest.is_empty());
    assert!(setup.voice_consent_signature.unwrap().rest.is_empty());
    let content = p.server_content.unwrap();
    assert!(content.rest.is_empty());
    assert!(content.grounding_metadata.unwrap().rest.is_empty());
    assert!(content.url_context_metadata.unwrap().rest.is_empty());
    for t in [
        content.input_transcription.unwrap(),
        content.output_transcription.unwrap(),
        content.interim_input_transcription.unwrap(),
    ] {
        assert!(t.rest.is_empty());
        assert!(t.words.unwrap()[0].rest.is_empty());
    }
    assert!(p.tool_call.unwrap().rest.is_empty());
    assert!(p.tool_call_cancellation.unwrap().rest.is_empty());
    assert!(p.go_away.unwrap().rest.is_empty());
    assert!(p.session_resumption_update.unwrap().rest.is_empty());
    let u = p.usage_metadata.unwrap();
    assert!(u.rest.is_empty());
    assert!(u.prompt_tokens_details.unwrap()[0].rest.is_empty());
    assert!(u.cache_tokens_details.unwrap()[0].rest.is_empty());
    assert!(u.response_tokens_details.unwrap()[0].rest.is_empty());
    assert!(u.tool_use_prompt_tokens_details.unwrap()[0].rest.is_empty());
    assert!(p.voice_activity.unwrap().rest.is_empty());
    assert!(p.voice_activity_detection_signal.unwrap().rest.is_empty());
}
#[test]
fn proto_defaults_aliases_false_zero_empty_and_null_have_distinct_wire_rules() {
    macro_rules! empty{($($ty:ty),*)=>{$(let p: $ty=serde_json::from_value(json!({})).unwrap();assert_eq!(serde_json::to_value(p).unwrap(),json!({}));)*};}
    empty!(
        LiveClientMessage,
        LiveServerMessage,
        BidiGenerateContentClientContent,
        BidiGenerateContentRealtimeInput,
        BidiGenerateContentToolResponse,
        BidiGenerateContentSetupComplete,
        BidiGenerateContentServerContent,
        BidiGenerateContentToolCall,
        BidiGenerateContentToolCallCancellation,
        GoAway,
        SessionResumptionUpdate,
        BidiGenerateContentTranscription,
        LiveBlob,
        RealtimeInputConfig,
        AutomaticActivityDetection,
        SessionResumptionConfig,
        ContextWindowCompressionConfig,
        SlidingWindow,
        AudioTranscriptionConfig,
        LiveUsageMetadata,
        LiveActivityStart,
        LiveActivityEnd,
        LanguageAuto,
        LanguageHints,
        HistoryConfig,
        ProactivityConfig,
        VoiceActivity,
        VoiceActivityDetectionSignal,
        WordInfo,
        LiveGenerationConfig
    );
    let wire = json!({"client_content":{"turn_complete":false,"turns":[]},"realtime_input":{"audio_stream_end":false,"activity_start":null},"setup":null});
    let p: LiveClientMessage = serde_json::from_value(wire).unwrap();
    assert_eq!(
        serde_json::to_value(p).unwrap(),
        json!({"clientContent":{"turnComplete":false,"turns":[]},"realtimeInput":{"audioStreamEnd":false}})
    );
    let p:LiveServerMessage=serde_json::from_value(json!({"server_content":{"generation_complete":false,"input_transcription":{"language_code":"en","finished":false}},"session_resumption_update":{"new_handle":"","resumable":false,"last_consumed_client_message_index":0},"usage_metadata":{"prompt_token_count":0}})).unwrap();
    assert_eq!(
        serde_json::to_value(p).unwrap(),
        json!({"serverContent":{"generationComplete":false,"inputTranscription":{"languageCode":"en","finished":false}},"sessionResumptionUpdate":{"newHandle":"","resumable":false,"lastConsumedClientMessageIndex":0},"usageMetadata":{"promptTokenCount":0}})
    );
    let p:AudioTranscriptionConfig=serde_json::from_value(json!({"language_codes":null,"custom_vocabulary":[],"word_timestamp":false,"diarization":null})).unwrap();
    assert_eq!(
        serde_json::to_value(p).unwrap(),
        json!({"customVocabulary":[],"wordTimestamp":false})
    );
    for value in [json!("1000"), json!(1000)] {
        round::<LiveInt64>(value);
    }
    assert!(serde_json::from_value::<LiveInt64>(json!("bad")).is_err());
}
#[test]
fn source_enums_and_unknown_extensions_do_not_create_new_discriminators() {
    for value in [
        "ACTIVITY_HANDLING_UNSPECIFIED",
        "START_OF_ACTIVITY_INTERRUPTS",
        "NO_INTERRUPTION",
    ] {
        round::<ActivityHandling>(json!(value));
    }
    for value in [
        "TURN_COVERAGE_UNSPECIFIED",
        "TURN_INCLUDES_ONLY_ACTIVITY",
        "TURN_INCLUDES_ALL_INPUT",
        "TURN_INCLUDES_AUDIO_ACTIVITY_AND_ALL_VIDEO",
    ] {
        round::<TurnCoverage>(json!(value));
    }
    for value in [
        "START_SENSITIVITY_UNSPECIFIED",
        "START_SENSITIVITY_HIGH",
        "START_SENSITIVITY_LOW",
    ] {
        round::<StartSensitivity>(json!(value));
    }
    for value in [
        "END_SENSITIVITY_UNSPECIFIED",
        "END_SENSITIVITY_HIGH",
        "END_SENSITIVITY_LOW",
    ] {
        round::<EndSensitivity>(json!(value));
    }
    for value in ["MODE_UNSPECIFIED", "VERBATIM", "SMART"] {
        round::<AudioTranscriptionMode>(json!(value));
    }
    for value in [
        "INTERACTION_STATUS_UNSPECIFIED",
        "IN_PROGRESS",
        "REQUIRES_ACTION",
        "IDLE",
    ] {
        round::<InteractionStatus>(json!(value));
    }
    for value in [
        "VAD_SIGNAL_TYPE_UNSPECIFIED",
        "VAD_SIGNAL_TYPE_SOS",
        "VAD_SIGNAL_TYPE_EOS",
    ] {
        round::<VadSignalType>(json!(value));
    }
    for value in ["TYPE_UNSPECIFIED", "ACTIVITY_START", "ACTIVITY_END"] {
        round::<VoiceActivityType>(json!(value));
    }
    assert!(serde_json::from_value::<AudioTranscriptionMode>(json!("future")).is_err());
    let p = round::<LiveServerMessage>(
        json!({"futureEvent":{"x":1},"usageMetadata":{"totalTokenCount":1}}),
    );
    assert_eq!(p.rest.len(), 1);
    let p = round::<BidiGenerateContentSetupComplete>(json!({"future":true}));
    assert_eq!(p.rest.len(), 1);
    round::<SpeechState>(json!("UNPUBLISHED_STATE"));
    round::<SpeechState>(json!(2));
    // The SDK explicitly excludes these Vertex-only options from mldev conversion.
    let p = round::<SessionResumptionConfig>(json!({"transparent":true}));
    assert!(p.rest.contains_key("transparent"));
}
#[test]
fn typed_builders_preserve_handshake_and_independent_websocket_connection() {
    let setup = BidiGenerateContentSetup::builder("models/live".into())
        .proactivity(ProactivityConfig::builder().proactive_audio(false).build())
        .generation_config(
            LiveGenerationConfig::builder()
                .enable_affective_dialog(true)
                .build(),
        )
        .build();
    let msg = LiveClientMessage::builder().setup(setup).build();
    assert_eq!(
        serde_json::to_value(msg).unwrap(),
        json!({"setup":{"model":"models/live","proactivity":{"proactiveAudio":false},"generationConfig":{"enableAffectiveDialog":true}}})
    );
    let request: LiveRequest = gproxy_protocol::WireRequest {
        method: http::Method::GET,
        path: "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent"
            .into(),
        query: Some("key=placeholder".into()),
        headers: http::HeaderMap::new(),
        body: (),
    };
    assert_eq!(request.method, http::Method::GET);
    assert!(request.path.starts_with("/ws/"));
    assert!(request.query.is_some());
    assert!(request.headers.is_empty());
    assert_eq!(request.body, ());
    let response: LiveResponse = gproxy_protocol::WireResponse {
        status: http::StatusCode::SWITCHING_PROTOCOLS,
        headers: http::HeaderMap::new(),
        body: (),
    };
    assert_eq!(response.status, http::StatusCode::SWITCHING_PROTOCOLS);
    assert_eq!(response.body, ());
    fn duplex(_: gproxy_protocol::WebSocket) {}
    let _ = duplex;
}

#[test]
fn all_documented_turn_completion_reasons_keep_exact_case() {
    for v in [
        "TURN_COMPLETE_REASON_UNSPECIFIED",
        "MALFORMED_FUNCTION_CALL",
        "RESPONSE_REJECTED",
        "NEED_MORE_INPUT",
        "PROHIBITED_INPUT_CONTENT",
        "IMAGE_PROHIBITED_INPUT_CONTENT",
        "INPUT_TEXT_CONTAIN_PROMINENT_PERSON_PROHIBITED",
        "INPUT_IMAGE_CELEBRITY",
        "INPUT_IMAGE_PHOTO_REALISTIC_CHILD_PROHIBITED",
        "INPUT_TEXT_NCII_PROHIBITED",
        "INPUT_OTHER",
        "INPUT_IP_PROHIBITED",
        "BLOCKLIST",
        "UNSAFE_PROMPT_FOR_IMAGE_GENERATION",
        "GENERATED_IMAGE_SAFETY",
        "GENERATED_CONTENT_SAFETY",
        "GENERATED_AUDIO_SAFETY",
        "GENERATED_VIDEO_SAFETY",
        "GENERATED_CONTENT_PROHIBITED",
        "GENERATED_CONTENT_BLOCKLIST",
        "GENERATED_IMAGE_PROHIBITED",
        "GENERATED_IMAGE_CELEBRITY",
        "GENERATED_IMAGE_PROMINENT_PEOPLE_DETECTED_BY_REWRITER",
        "GENERATED_IMAGE_IDENTIFIABLE_PEOPLE",
        "GENERATED_IMAGE_MINORS",
        "OUTPUT_IMAGE_IP_PROHIBITED",
        "GENERATED_OTHER",
        "MAX_REGENERATION_REACHED",
    ] {
        round::<TurnCompleteReason>(json!(v));
    }
    assert!(serde_json::from_value::<TurnCompleteReason>(json!("need_more_input")).is_err());
}

#[test]
fn every_live_specific_field_recognizes_proto_spelling_and_null_as_unset() {
    fn check<T: Serialize + DeserializeOwned>(base: Value, fields: &[&str]) {
        for field in fields {
            let mut camel = String::new();
            let mut upper = false;
            for c in field.chars() {
                if c == '_' {
                    upper = true;
                } else if upper {
                    camel.push(c.to_ascii_uppercase());
                    upper = false;
                } else {
                    camel.push(c);
                }
            }
            for key in [field.to_string(), camel] {
                let mut wire = base.clone();
                wire[&key] = Value::Null;
                let p: T = serde_json::from_value(wire).unwrap();
                assert_eq!(
                    serde_json::to_value(p).unwrap(),
                    base,
                    "{} {key}",
                    std::any::type_name::<T>()
                );
            }
        }
    }
    check::<LiveClientMessage>(
        json!({}),
        &["setup", "client_content", "realtime_input", "tool_response"],
    );
    check::<LiveServerMessage>(
        json!({}),
        &[
            "setup_complete",
            "server_content",
            "tool_call",
            "tool_call_cancellation",
            "usage_metadata",
            "go_away",
            "session_resumption_update",
            "voice_activity_detection_signal",
            "voice_activity",
        ],
    );
    check::<BidiGenerateContentSetup>(
        json!({"model":"m"}),
        &[
            "generation_config",
            "system_instruction",
            "tools",
            "realtime_input_config",
            "session_resumption",
            "context_window_compression",
            "input_audio_transcription",
            "output_audio_transcription",
            "proactivity",
            "history_config",
            "avatar_config",
            "safety_settings",
        ],
    );
    check::<BidiGenerateContentClientContent>(json!({}), &["turns", "turn_complete"]);
    check::<BidiGenerateContentRealtimeInput>(
        json!({}),
        &[
            "media_chunks",
            "audio",
            "audio_stream_end",
            "video",
            "text",
            "activity_start",
            "activity_end",
            "media_resolution",
        ],
    );
    check::<BidiGenerateContentToolResponse>(json!({}), &["function_responses"]);
    check::<BidiGenerateContentSetupComplete>(
        json!({}),
        &["session_id", "voice_consent_signature"],
    );
    check::<BidiGenerateContentServerContent>(
        json!({}),
        &[
            "model_turn",
            "generation_complete",
            "turn_complete",
            "interrupted",
            "grounding_metadata",
            "input_transcription",
            "output_transcription",
            "interim_input_transcription",
            "url_context_metadata",
            "waiting_for_input",
            "turn_complete_reason",
            "interaction_status",
            "speech_state",
        ],
    );
    check::<BidiGenerateContentToolCall>(json!({}), &["function_calls"]);
    check::<BidiGenerateContentToolCallCancellation>(json!({}), &["ids"]);
    check::<GoAway>(json!({}), &["time_left"]);
    check::<SessionResumptionUpdate>(
        json!({}),
        &[
            "new_handle",
            "resumable",
            "last_consumed_client_message_index",
        ],
    );
    check::<BidiGenerateContentTranscription>(
        json!({}),
        &[
            "text",
            "finished",
            "language_code",
            "speaker_label",
            "words",
        ],
    );
    check::<WordInfo>(json!({}), &["word", "start_offset", "end_offset"]);
    check::<LiveUsageMetadata>(
        json!({}),
        &[
            "prompt_token_count",
            "cached_content_token_count",
            "response_token_count",
            "tool_use_prompt_token_count",
            "thoughts_token_count",
            "total_token_count",
            "prompt_tokens_details",
            "cache_tokens_details",
            "response_tokens_details",
            "tool_use_prompt_tokens_details",
            "service_tier",
        ],
    );
    check::<VoiceActivityDetectionSignal>(json!({}), &["vad_signal_type"]);
    check::<VoiceActivity>(json!({}), &["type", "audio_offset"]);
    check::<RealtimeInputConfig>(
        json!({}),
        &[
            "automatic_activity_detection",
            "activity_handling",
            "turn_coverage",
        ],
    );
    check::<AutomaticActivityDetection>(
        json!({}),
        &[
            "disabled",
            "start_of_speech_sensitivity",
            "end_of_speech_sensitivity",
            "prefix_padding_ms",
            "silence_duration_ms",
        ],
    );
    check::<SessionResumptionConfig>(json!({}), &["handle"]);
    check::<ContextWindowCompressionConfig>(json!({}), &["trigger_tokens", "sliding_window"]);
    check::<SlidingWindow>(json!({}), &["target_tokens"]);
    check::<AudioTranscriptionConfig>(
        json!({}),
        &[
            "language_codes",
            "language_auto",
            "language_hints",
            "custom_vocabulary",
            "adaptation_phrases",
            "word_timestamp",
            "diarization",
            "mode",
        ],
    );
    check::<LanguageHints>(json!({}), &["language_codes"]);
    check::<ProactivityConfig>(json!({}), &["proactive_audio"]);
    check::<HistoryConfig>(json!({}), &["initial_history_in_client_content"]);
    check::<AvatarConfig>(
        json!({}),
        &[
            "avatar_name",
            "customized_avatar",
            "audio_bitrate_bps",
            "video_bitrate_bps",
        ],
    );
    check::<CustomizedAvatar>(json!({}), &["image_mime_type", "image_data"]);
    check::<LiveSpeechConfig>(
        json!({}),
        &[
            "voice_config",
            "multi_speaker_voice_config",
            "language_code",
        ],
    );
    check::<LiveVoiceConfig>(
        json!({}),
        &["replicated_voice_config", "prebuilt_voice_config"],
    );
    check::<LivePrebuiltVoiceConfig>(json!({}), &["voice_name"]);
    check::<LiveMultiSpeakerVoiceConfig>(json!({}), &["speaker_voice_configs"]);
    check::<LiveSpeakerVoiceConfig>(json!({}), &["speaker", "voice_config"]);
    check::<ReplicatedVoiceConfig>(
        json!({}),
        &[
            "mime_type",
            "voice_sample_audio",
            "consent_audio",
            "voice_consent_signature",
        ],
    );
    check::<VoiceConsentSignature>(json!({}), &["signature"]);
    check::<TranslationConfig>(json!({}), &["echo_target_language", "target_language_code"]);
    check::<LiveGenerationConfig>(
        json!({}),
        &[
            "enable_affective_dialog",
            "translation_config",
            "audio_transcription_config",
            "speech_config",
            "media_resolution",
            "response_modalities",
            "candidate_count",
            "max_output_tokens",
            "top_p",
            "top_k",
            "presence_penalty",
            "frequency_penalty",
            "thinking_config",
        ],
    );
    check::<LivePart>(json!({}), &["audio_transcription", "media_processing"]);
}
#[test]
fn live_parts_and_prebuilt_multispeaker_voices_use_current_developer_shapes() {
    let content = json!({"modelTurn":{"parts":[{"inlineData":{"mimeType":"audio/pcm","data":"AA=="},"audioTranscription":{"text":"hello","languageCode":"en"},"mediaProcessing":"STATIC"}]}});
    let p = round::<BidiGenerateContentServerContent>(content);
    let p = &p.model_turn.as_ref().unwrap().parts.as_ref().unwrap()[0];
    assert!(p.rest.is_empty());
    assert!(p.audio_transcription.as_ref().unwrap().rest.is_empty());
    assert!(p.inline_data.as_ref().unwrap().rest.is_empty());
    let voice = json!({"multiSpeakerVoiceConfig":{"speakerVoiceConfigs":[{"speaker":"A","voiceConfig":{"prebuiltVoiceConfig":{"voiceName":"Kore"}}}]}});
    let p = round::<LiveSpeechConfig>(voice);
    assert!(p.rest.is_empty());
    let p = p.multi_speaker_voice_config.unwrap();
    assert!(p.rest.is_empty());
    let p = &p.speaker_voice_configs.as_ref().unwrap()[0];
    assert!(p.rest.is_empty());
    assert!(p.voice_config.as_ref().unwrap().rest.is_empty());
    assert!(
        p.voice_config
            .as_ref()
            .unwrap()
            .prebuilt_voice_config
            .as_ref()
            .unwrap()
            .rest
            .is_empty()
    );
    for name in ["MEDIA_PROCESSING_UNSPECIFIED", "STATIC", "AGENTIC"] {
        round::<LiveMediaProcessing>(json!(name));
    }
}