From 3f38214ebc4c1f9ca44cdab675de627368af3f19 Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Wed, 18 Mar 2026 15:20:33 -0700 Subject: [PATCH] feat: voice consent signature types across all SDK languages. Most languages capture the "setup complete" message as a property on the session object. Golang returns the setup complete to the user as the first message. So there's no need to capture it, and that would be a breaking change. PiperOrigin-RevId: 885814555 --- examples/output.wav | Bin 0 -> 126286 bytes .../examples/LiveAudioConversationAsync.java | 156 +++++++++++++++--- src/main/java/com/google/genai/AsyncLive.java | 5 +- .../java/com/google/genai/AsyncSession.java | 13 +- .../java/com/google/genai/LiveConverters.java | 139 +++++++++++++++- src/main/java/com/google/genai/Models.java | 151 ++++++++++++++++- src/main/java/com/google/genai/Tunings.java | 143 +++++++++++++++- .../genai/types/LiveServerSetupComplete.java | 43 +++++ .../genai/types/ReplicatedVoiceConfig.java | 70 ++++++++ .../genai/types/VoiceConsentSignature.java | 81 +++++++++ .../java/com/google/genai/AsyncLiveTest.java | 23 +++ 11 files changed, 795 insertions(+), 29 deletions(-) create mode 100644 examples/output.wav create mode 100644 src/main/java/com/google/genai/types/VoiceConsentSignature.java diff --git a/examples/output.wav b/examples/output.wav new file mode 100644 index 0000000000000000000000000000000000000000..64e21955fa0b08c8a8b3c564d617fb3fe0036317 GIT binary patch literal 126286 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

JKFTxW2GuO;roXQ}r> z{kyd1zWr~HKcD~irCv4j+k5`_Ea(xJd)qs$^9*eZ<~@D&ss5~Y4efreBY2$E_aSs! z`d(9S<7e6YJ1qWwq}N$IhWA{b-LIHzT=$Oh_4A ztKT=1ZykM>*L~&*u4(VL_TK28&G2h!rzL1(Ttm-i{n5<7*5|k9Uq|<~@O8{P{8(rAeUIJu#J?Tov(;~} zbKTMHd3^ua!T@So*cLA$v{vzVLg@+%x%p+*+{w+t!}fxh-=W z`)KU_25Z{fZC%D5dwkT_c@r+<9krO*5QsL!~C z9_P7J?)L?^@m-4d-R9XVLm!XyS+3Q8mUEl-4CiawZRz{i?I`~{Vupvcys_ckFuSIF zd-!pj_w)KFenn@8uXeh_J8$35HgEg?c-*&TckFEI`$D_^$9=w^yX*gv_wShjvAq5H zXe75KpY7^4H~;?qwy%#ywexOK-l^YyKkHxp#~D8J&(O8?+Y$f!2z=bawupd%d+urloIf_$j?n9@JRjpS$eqZkW`0nK# R!?W?;r=6w$=ur5N{|j@zi5>s| literal 0 HcmV?d00001 diff --git a/examples/src/main/java/com/google/genai/examples/LiveAudioConversationAsync.java b/examples/src/main/java/com/google/genai/examples/LiveAudioConversationAsync.java index e1a6c82e0a9..077f01ff1cd 100644 --- a/examples/src/main/java/com/google/genai/examples/LiveAudioConversationAsync.java +++ b/examples/src/main/java/com/google/genai/examples/LiveAudioConversationAsync.java @@ -94,6 +94,8 @@ public final class LiveAudioConversationAsync { private static SourceDataLine speakerLine; private static AsyncSession session; private static ExecutorService micExecutor = Executors.newSingleThreadExecutor(); + private static String promptString = null; + private static java.io.ByteArrayOutputStream audioResponseBytes = new java.io.ByteArrayOutputStream(); /** Creates the parameters for sending an audio chunk. */ public static LiveSendRealtimeInputParameters createAudioContent(byte[] audioData) { @@ -155,9 +157,54 @@ public static void main(String[] args) throws LineUnavailableException { System.out.println("Using Gemini Developer API"); } + String getModelFromArgs = null; + String voiceSamplePath = null; + String voiceConsentPath = null; + String voiceSignature = null; + promptString = null; + + for (String arg : args) { + if (arg.startsWith("--model=")) { + getModelFromArgs = arg.substring("--model=".length()); + } else if (arg.startsWith("--voice-sample=")) { + voiceSamplePath = arg.substring("--voice-sample=".length()); + } else if (arg.startsWith("--voice-consent=")) { + voiceConsentPath = arg.substring("--voice-consent=".length()); + } else if (arg.startsWith("--voice-signature=")) { + voiceSignature = arg.substring("--voice-signature=".length()); + } else if (arg.startsWith("--prompt=")) { + promptString = arg.substring("--prompt=".length()); + } else if (!arg.startsWith("--") && getModelFromArgs == null) { + getModelFromArgs = arg; + } + } + + byte[] voiceSampleAudio = null; + byte[] consentAudio = null; + + if (voiceSamplePath != null) { + try { + voiceSampleAudio = java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(voiceSamplePath)); + } catch (java.io.IOException e) { + throw new RuntimeException("Failed to read voice sample: " + e.getMessage()); + } + if (voiceConsentPath == null && voiceSignature == null) { + throw new IllegalArgumentException( + "Either --voice-consent or --voice-signature must be provided when --voice-sample is" + + " used."); + } + } + if (voiceConsentPath != null) { + try { + consentAudio = java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(voiceConsentPath)); + } catch (java.io.IOException e) { + throw new RuntimeException("Failed to read voice consent: " + e.getMessage()); + } + } + final String modelId; - if (args.length != 0) { - modelId = args[0]; + if (getModelFromArgs != null) { + modelId = getModelFromArgs; } else if (client.vertexAI()) { modelId = Constants.GEMINI_LIVE_MODEL_NAME; } else { @@ -165,21 +212,40 @@ public static void main(String[] args) throws LineUnavailableException { } // --- Audio Line Setup --- - microphoneLine = getMicrophoneLine(); - speakerLine = getSpeakerLine(); + if (promptString == null) { + microphoneLine = getMicrophoneLine(); + speakerLine = getSpeakerLine(); + } // --- Live API Config for Audio --- // Choice of ["Aoede", "Puck", "Charon", "Kore", "Fenrir", "Leda", "Orus", "Zephyr"] String voiceName = "Aoede"; + + VoiceConfig.Builder voiceConfigBuilder = VoiceConfig.builder(); + if (voiceSampleAudio != null) { + com.google.genai.types.ReplicatedVoiceConfig.Builder repBuilder = + com.google.genai.types.ReplicatedVoiceConfig.builder() + .mimeType("audio/wav") + .voiceSampleAudio(voiceSampleAudio); + if (consentAudio != null) { + repBuilder.consentAudio(consentAudio); + } + if (voiceSignature != null) { + repBuilder.voiceConsentSignature( + com.google.genai.types.VoiceConsentSignature.builder().signature(voiceSignature)); + } + voiceConfigBuilder.replicatedVoiceConfig(repBuilder); + } else { + voiceConfigBuilder.prebuiltVoiceConfig( + PrebuiltVoiceConfig.builder().voiceName(voiceName)); + } + LiveConnectConfig config = LiveConnectConfig.builder() .responseModalities(Modality.Known.AUDIO) .speechConfig( SpeechConfig.builder() - .voiceConfig( - VoiceConfig.builder() - .prebuiltVoiceConfig( - PrebuiltVoiceConfig.builder().voiceName(voiceName))) + .voiceConfig(voiceConfigBuilder) .languageCode("en-US")) .realtimeInputConfig( RealtimeInputConfig.builder() @@ -232,19 +298,33 @@ public static void main(String[] args) throws LineUnavailableException { session = client.async.live.connect(modelId, config).get(); System.out.println("Connected."); - // --- Start Audio Lines --- - microphoneLine.start(); - speakerLine.start(); - System.out.println("Microphone and speakers started. Speak now (Press Ctrl+C to exit)..."); + if (session.setupComplete() != null && session.setupComplete().voiceConsentSignature().isPresent()) { + System.out.println( + "\n=== Voice Consent Signature Received ===\n" + + session.setupComplete().voiceConsentSignature().get().signature().orElse("") + + "\n========================================\n"); + } // --- Start Receiving Audio Responses --- CompletableFuture receiveFuture = session.receive(LiveAudioConversationAsync::handleAudioResponse); - System.err.println("Receive stream started."); // Add this line - - // --- Start Sending Microphone Audio --- - CompletableFuture sendFuture = - CompletableFuture.runAsync(LiveAudioConversationAsync::sendMicrophoneAudio, micExecutor); + System.err.println("Receive stream started."); + + CompletableFuture sendFuture; + if (promptString == null) { + // --- Start Audio Lines --- + microphoneLine.start(); + speakerLine.start(); + System.out.println("Microphone and speakers started. Speak now (Press Ctrl+C to exit)..."); + + // --- Start Sending Microphone Audio --- + sendFuture = CompletableFuture.runAsync(LiveAudioConversationAsync::sendMicrophoneAudio, micExecutor); + } else { + System.out.println("Sending prompt: " + promptString); + session.sendRealtimeInput( + LiveSendRealtimeInputParameters.builder().text(promptString).build()).get(); + sendFuture = CompletableFuture.completedFuture(null); + } // Keep the main thread alive. Wait for sending or receiving to finish (or // error). @@ -313,13 +393,19 @@ public static void handleAudioResponse(LiveServerMessage message) { content -> { // Handle interruptions from Gemini. if (content.interrupted().orElse(false)) { - speakerLine.flush(); + if (speakerLine != null && speakerLine.isOpen()) { + speakerLine.flush(); + } return; // Skip processing the rest of this message's audio. } // Handle Model turn completion. if (content.turnComplete().orElse(false)) { - // The turn is over, no more audio will be sent for this turn. + if (promptString != null) { + saveWavFile(); + System.out.println("Response received, exiting."); + System.exit(0); + } return; } @@ -334,15 +420,45 @@ public static void handleAudioResponse(LiveServerMessage message) { if (speakerLine != null && speakerLine.isOpen()) { // Write audio data to the speaker speakerLine.write(audioBytes, 0, audioBytes.length); + } else { + System.out.println( + "Received audio response chunk: " + audioBytes.length + " bytes."); + } + try { + audioResponseBytes.write(audioBytes); + } catch (java.io.IOException e) { + System.err.println("Failed to accumulate audio bytes: " + e.getMessage()); } }); // If this is the last message of a generation, drain the buffer. if (content.generationComplete().orElse(false)) { - speakerLine.drain(); + if (speakerLine != null && speakerLine.isOpen()) { + speakerLine.drain(); + } } }); } + private static void saveWavFile() { + byte[] audioData = audioResponseBytes.toByteArray(); + if (audioData.length == 0) { + System.out.println("No audio data received to save."); + return; + } + try { + javax.sound.sampled.AudioInputStream ais = new javax.sound.sampled.AudioInputStream( + new java.io.ByteArrayInputStream(audioData), + SPEAKER_AUDIO_FORMAT, + audioData.length / SPEAKER_AUDIO_FORMAT.getFrameSize() + ); + java.io.File outputFile = new java.io.File("output.wav"); + javax.sound.sampled.AudioSystem.write(ais, javax.sound.sampled.AudioFileFormat.Type.WAVE, outputFile); + System.out.println("Saved audio response to " + outputFile.getAbsolutePath()); + } catch (Exception e) { + System.err.println("Failed to save WAV file: " + e.getMessage()); + } + } + private LiveAudioConversationAsync() {} } diff --git a/src/main/java/com/google/genai/AsyncLive.java b/src/main/java/com/google/genai/AsyncLive.java index 0c1b577d297..fdebcaa8b5d 100644 --- a/src/main/java/com/google/genai/AsyncLive.java +++ b/src/main/java/com/google/genai/AsyncLive.java @@ -25,6 +25,7 @@ import com.google.genai.types.LiveConnectConfig; import com.google.genai.types.LiveConnectParameters; import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.LiveServerSetupComplete; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -283,11 +284,13 @@ private void handleIncomingMessage(String message) { try { LiveServerMessage initialResponse = LiveServerMessage.fromJson(message); if (initialResponse.setupComplete().isPresent()) { + LiveServerSetupComplete setupComplete = initialResponse.setupComplete().get(); sessionFuture.complete( new AsyncSession( apiClient, this, - initialResponse.setupComplete().get().sessionId().orElse(null))); + setupComplete.sessionId().orElse(null), + setupComplete)); } else { sessionFuture.completeExceptionally( new GenAiIOException( diff --git a/src/main/java/com/google/genai/AsyncSession.java b/src/main/java/com/google/genai/AsyncSession.java index 8047efc068c..737369f7d10 100644 --- a/src/main/java/com/google/genai/AsyncSession.java +++ b/src/main/java/com/google/genai/AsyncSession.java @@ -21,6 +21,7 @@ import com.google.genai.types.LiveClientContent; import com.google.genai.types.LiveClientMessage; import com.google.genai.types.LiveClientToolResponse; +import com.google.genai.types.LiveServerSetupComplete; import com.google.genai.types.LiveSendClientContentParameters; import com.google.genai.types.LiveSendRealtimeInputParameters; import com.google.genai.types.LiveSendToolResponseParameters; @@ -40,11 +41,17 @@ public final class AsyncSession { private final AsyncLive.GenAiWebSocketClient websocket; final String sessionId; + private final LiveServerSetupComplete setupComplete; - AsyncSession(ApiClient apiClient, AsyncLive.GenAiWebSocketClient websocket, String sessionId) { + AsyncSession( + ApiClient apiClient, + AsyncLive.GenAiWebSocketClient websocket, + String sessionId, + LiveServerSetupComplete setupComplete) { this.apiClient = apiClient; this.websocket = websocket; this.sessionId = sessionId; + this.setupComplete = setupComplete; } /** @@ -145,4 +152,8 @@ public CompletableFuture close() { public String sessionId() { return sessionId; } + + public LiveServerSetupComplete setupComplete() { + return setupComplete; + } } diff --git a/src/main/java/com/google/genai/LiveConverters.java b/src/main/java/com/google/genai/LiveConverters.java index febb094e1e5..caf86fa2c2d 100644 --- a/src/main/java/com/google/genai/LiveConverters.java +++ b/src/main/java/com/google/genai/LiveConverters.java @@ -382,7 +382,10 @@ ObjectNode generationConfigToVertex(JsonNode fromObject, ObjectNode parentObject Common.setValueByPath( toObject, new String[] {"speechConfig"}, - Common.getValueByPath(fromObject, new String[] {"speechConfig"})); + speechConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"speechConfig"})), + toObject)); } if (Common.getValueByPath(fromObject, new String[] {"stopSequences"}) != null) { @@ -1219,8 +1222,11 @@ ObjectNode liveConnectConfigToVertex(JsonNode fromObject, ObjectNode parentObjec Common.setValueByPath( parentObject, new String[] {"setup", "generationConfig", "speechConfig"}, - Transformers.tLiveSpeechConfig( - Common.getValueByPath(fromObject, new String[] {"speechConfig"}))); + speechConfigToVertex( + JsonSerializable.toJsonNode( + Transformers.tLiveSpeechConfig( + Common.getValueByPath(fromObject, new String[] {"speechConfig"}))), + toObject)); } if (Common.getValueByPath(fromObject, new String[] {"thinkingConfig"}) != null) { @@ -1648,6 +1654,24 @@ ObjectNode liveServerMessageFromVertex(JsonNode fromObject, ObjectNode parentObj return toObject; } + @ExcludeFromGeneratedCoverageReport + ObjectNode multiSpeakerVoiceConfigToVertex(JsonNode fromObject, ObjectNode parentObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"speakerVoiceConfigs"}) != null) { + ArrayNode keyArray = + (ArrayNode) Common.getValueByPath(fromObject, new String[] {"speakerVoiceConfigs"}); + ObjectMapper objectMapper = new ObjectMapper(); + ArrayNode result = objectMapper.createArrayNode(); + + for (JsonNode item : keyArray) { + result.add(speakerVoiceConfigToVertex(JsonSerializable.toJsonNode(item), toObject)); + } + Common.setValueByPath(toObject, new String[] {"speakerVoiceConfigs"}, result); + } + + return toObject; + } + @ExcludeFromGeneratedCoverageReport ObjectNode partToMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); @@ -1859,6 +1883,36 @@ ObjectNode partToVertex(JsonNode fromObject, ObjectNode parentObject) { return toObject; } + @ExcludeFromGeneratedCoverageReport + ObjectNode replicatedVoiceConfigToVertex(JsonNode fromObject, ObjectNode parentObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"mimeType"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"mimeType"}, + Common.getValueByPath(fromObject, new String[] {"mimeType"})); + } + + if (Common.getValueByPath(fromObject, new String[] {"voiceSampleAudio"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"voiceSampleAudio"}, + Common.getValueByPath(fromObject, new String[] {"voiceSampleAudio"})); + } + + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"consentAudio"}))) { + throw new IllegalArgumentException( + "consentAudio parameter is not supported in Gemini Enterprise Agent Platform."); + } + + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"voiceConsentSignature"}))) { + throw new IllegalArgumentException( + "voiceConsentSignature parameter is not supported in Gemini Enterprise Agent Platform."); + } + + return toObject; + } + @ExcludeFromGeneratedCoverageReport ObjectNode safetySettingToMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); @@ -1900,6 +1954,62 @@ ObjectNode sessionResumptionConfigToMldev(JsonNode fromObject, ObjectNode parent return toObject; } + @ExcludeFromGeneratedCoverageReport + ObjectNode speakerVoiceConfigToVertex(JsonNode fromObject, ObjectNode parentObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"speaker"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"speaker"}, + Common.getValueByPath(fromObject, new String[] {"speaker"})); + } + + if (Common.getValueByPath(fromObject, new String[] {"voiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"voiceConfig"}, + voiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"voiceConfig"})), + toObject)); + } + + return toObject; + } + + @ExcludeFromGeneratedCoverageReport + ObjectNode speechConfigToVertex(JsonNode fromObject, ObjectNode parentObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"voiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"voiceConfig"}, + voiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"voiceConfig"})), + toObject)); + } + + if (Common.getValueByPath(fromObject, new String[] {"languageCode"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"languageCode"}, + Common.getValueByPath(fromObject, new String[] {"languageCode"})); + } + + if (Common.getValueByPath(fromObject, new String[] {"multiSpeakerVoiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"multiSpeakerVoiceConfig"}, + multiSpeakerVoiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"multiSpeakerVoiceConfig"})), + toObject)); + } + + return toObject; + } + @ExcludeFromGeneratedCoverageReport ObjectNode toolToMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); @@ -2188,4 +2298,27 @@ ObjectNode voiceActivityFromVertex(JsonNode fromObject, ObjectNode parentObject) return toObject; } + + @ExcludeFromGeneratedCoverageReport + ObjectNode voiceConfigToVertex(JsonNode fromObject, ObjectNode parentObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"replicatedVoiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"replicatedVoiceConfig"}, + replicatedVoiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"replicatedVoiceConfig"})), + toObject)); + } + + if (Common.getValueByPath(fromObject, new String[] {"prebuiltVoiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"prebuiltVoiceConfig"}, + Common.getValueByPath(fromObject, new String[] {"prebuiltVoiceConfig"})); + } + + return toObject; + } } diff --git a/src/main/java/com/google/genai/Models.java b/src/main/java/com/google/genai/Models.java index a6fb051345d..16f89763ca3 100644 --- a/src/main/java/com/google/genai/Models.java +++ b/src/main/java/com/google/genai/Models.java @@ -1863,8 +1863,12 @@ ObjectNode generateContentConfigToVertex( Common.setValueByPath( toObject, new String[] {"speechConfig"}, - Transformers.tSpeechConfig( - Common.getValueByPath(fromObject, new String[] {"speechConfig"}))); + speechConfigToVertex( + JsonSerializable.toJsonNode( + Transformers.tSpeechConfig( + Common.getValueByPath(fromObject, new String[] {"speechConfig"}))), + toObject, + rootObject)); } if (Common.getValueByPath(fromObject, new String[] {"audioTimestamp"}) != null) { @@ -3392,7 +3396,11 @@ ObjectNode generationConfigToVertex( Common.setValueByPath( toObject, new String[] {"speechConfig"}, - Common.getValueByPath(fromObject, new String[] {"speechConfig"})); + speechConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"speechConfig"})), + toObject, + rootObject)); } if (Common.getValueByPath(fromObject, new String[] {"stopSequences"}) != null) { @@ -4109,6 +4117,26 @@ ObjectNode modelFromVertex(JsonNode fromObject, ObjectNode parentObject, JsonNod return toObject; } + @ExcludeFromGeneratedCoverageReport + ObjectNode multiSpeakerVoiceConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"speakerVoiceConfigs"}) != null) { + ArrayNode keyArray = + (ArrayNode) Common.getValueByPath(fromObject, new String[] {"speakerVoiceConfigs"}); + ObjectMapper objectMapper = new ObjectMapper(); + ArrayNode result = objectMapper.createArrayNode(); + + for (JsonNode item : keyArray) { + result.add( + speakerVoiceConfigToVertex(JsonSerializable.toJsonNode(item), toObject, rootObject)); + } + Common.setValueByPath(toObject, new String[] {"speakerVoiceConfigs"}, result); + } + + return toObject; + } + @ExcludeFromGeneratedCoverageReport ObjectNode partToMldev(JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); @@ -4584,6 +4612,37 @@ ObjectNode referenceImageAPIToVertex( return toObject; } + @ExcludeFromGeneratedCoverageReport + ObjectNode replicatedVoiceConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"mimeType"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"mimeType"}, + Common.getValueByPath(fromObject, new String[] {"mimeType"})); + } + + if (Common.getValueByPath(fromObject, new String[] {"voiceSampleAudio"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"voiceSampleAudio"}, + Common.getValueByPath(fromObject, new String[] {"voiceSampleAudio"})); + } + + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"consentAudio"}))) { + throw new IllegalArgumentException( + "consentAudio parameter is not supported in Gemini Enterprise Agent Platform."); + } + + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"voiceConsentSignature"}))) { + throw new IllegalArgumentException( + "voiceConsentSignature parameter is not supported in Gemini Enterprise Agent Platform."); + } + + return toObject; + } + @ExcludeFromGeneratedCoverageReport ObjectNode safetyAttributesFromMldev( JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { @@ -4824,6 +4883,67 @@ ObjectNode segmentImageSourceToVertex( return toObject; } + @ExcludeFromGeneratedCoverageReport + ObjectNode speakerVoiceConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"speaker"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"speaker"}, + Common.getValueByPath(fromObject, new String[] {"speaker"})); + } + + if (Common.getValueByPath(fromObject, new String[] {"voiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"voiceConfig"}, + voiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"voiceConfig"})), + toObject, + rootObject)); + } + + return toObject; + } + + @ExcludeFromGeneratedCoverageReport + ObjectNode speechConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"voiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"voiceConfig"}, + voiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"voiceConfig"})), + toObject, + rootObject)); + } + + if (Common.getValueByPath(fromObject, new String[] {"languageCode"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"languageCode"}, + Common.getValueByPath(fromObject, new String[] {"languageCode"})); + } + + if (Common.getValueByPath(fromObject, new String[] {"multiSpeakerVoiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"multiSpeakerVoiceConfig"}, + multiSpeakerVoiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"multiSpeakerVoiceConfig"})), + toObject, + rootObject)); + } + + return toObject; + } + @ExcludeFromGeneratedCoverageReport ObjectNode toolConfigToMldev(JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); @@ -5539,6 +5659,31 @@ ObjectNode videoToVertex(JsonNode fromObject, ObjectNode parentObject, JsonNode return toObject; } + @ExcludeFromGeneratedCoverageReport + ObjectNode voiceConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"replicatedVoiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"replicatedVoiceConfig"}, + replicatedVoiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"replicatedVoiceConfig"})), + toObject, + rootObject)); + } + + if (Common.getValueByPath(fromObject, new String[] {"prebuiltVoiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"prebuiltVoiceConfig"}, + Common.getValueByPath(fromObject, new String[] {"prebuiltVoiceConfig"})); + } + + return toObject; + } + /** A shared buildRequest method for both sync and async methods. */ BuiltRequest buildRequestForPrivateGenerateContent( String model, List contents, GenerateContentConfig config) { diff --git a/src/main/java/com/google/genai/Tunings.java b/src/main/java/com/google/genai/Tunings.java index f9b6bc2b74f..5c6970b0b93 100644 --- a/src/main/java/com/google/genai/Tunings.java +++ b/src/main/java/com/google/genai/Tunings.java @@ -1005,7 +1005,11 @@ ObjectNode generationConfigToVertex( Common.setValueByPath( toObject, new String[] {"speechConfig"}, - Common.getValueByPath(fromObject, new String[] {"speechConfig"})); + speechConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"speechConfig"})), + toObject, + rootObject)); } if (Common.getValueByPath(fromObject, new String[] {"stopSequences"}) != null) { @@ -1159,6 +1163,118 @@ ObjectNode listTuningJobsResponseFromVertex( return toObject; } + @ExcludeFromGeneratedCoverageReport + ObjectNode multiSpeakerVoiceConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"speakerVoiceConfigs"}) != null) { + ArrayNode keyArray = + (ArrayNode) Common.getValueByPath(fromObject, new String[] {"speakerVoiceConfigs"}); + ObjectMapper objectMapper = new ObjectMapper(); + ArrayNode result = objectMapper.createArrayNode(); + + for (JsonNode item : keyArray) { + result.add( + speakerVoiceConfigToVertex(JsonSerializable.toJsonNode(item), toObject, rootObject)); + } + Common.setValueByPath(toObject, new String[] {"speakerVoiceConfigs"}, result); + } + + return toObject; + } + + @ExcludeFromGeneratedCoverageReport + ObjectNode replicatedVoiceConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"mimeType"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"mimeType"}, + Common.getValueByPath(fromObject, new String[] {"mimeType"})); + } + + if (Common.getValueByPath(fromObject, new String[] {"voiceSampleAudio"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"voiceSampleAudio"}, + Common.getValueByPath(fromObject, new String[] {"voiceSampleAudio"})); + } + + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"consentAudio"}))) { + throw new IllegalArgumentException( + "consentAudio parameter is not supported in Gemini Enterprise Agent Platform."); + } + + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"voiceConsentSignature"}))) { + throw new IllegalArgumentException( + "voiceConsentSignature parameter is not supported in Gemini Enterprise Agent Platform."); + } + + return toObject; + } + + @ExcludeFromGeneratedCoverageReport + ObjectNode speakerVoiceConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"speaker"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"speaker"}, + Common.getValueByPath(fromObject, new String[] {"speaker"})); + } + + if (Common.getValueByPath(fromObject, new String[] {"voiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"voiceConfig"}, + voiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"voiceConfig"})), + toObject, + rootObject)); + } + + return toObject; + } + + @ExcludeFromGeneratedCoverageReport + ObjectNode speechConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"voiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"voiceConfig"}, + voiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"voiceConfig"})), + toObject, + rootObject)); + } + + if (Common.getValueByPath(fromObject, new String[] {"languageCode"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"languageCode"}, + Common.getValueByPath(fromObject, new String[] {"languageCode"})); + } + + if (Common.getValueByPath(fromObject, new String[] {"multiSpeakerVoiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"multiSpeakerVoiceConfig"}, + multiSpeakerVoiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"multiSpeakerVoiceConfig"})), + toObject, + rootObject)); + } + + return toObject; + } + @ExcludeFromGeneratedCoverageReport ObjectNode tunedModelFromMldev( JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { @@ -1649,6 +1765,31 @@ ObjectNode tuningValidationDatasetToVertex( return toObject; } + @ExcludeFromGeneratedCoverageReport + ObjectNode voiceConfigToVertex( + JsonNode fromObject, ObjectNode parentObject, JsonNode rootObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + if (Common.getValueByPath(fromObject, new String[] {"replicatedVoiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"replicatedVoiceConfig"}, + replicatedVoiceConfigToVertex( + JsonSerializable.toJsonNode( + Common.getValueByPath(fromObject, new String[] {"replicatedVoiceConfig"})), + toObject, + rootObject)); + } + + if (Common.getValueByPath(fromObject, new String[] {"prebuiltVoiceConfig"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"prebuiltVoiceConfig"}, + Common.getValueByPath(fromObject, new String[] {"prebuiltVoiceConfig"})); + } + + return toObject; + } + /** A shared buildRequest method for both sync and async methods. */ BuiltRequest buildRequestForPrivateGet(String name, GetTuningJobConfig config) { diff --git a/src/main/java/com/google/genai/types/LiveServerSetupComplete.java b/src/main/java/com/google/genai/types/LiveServerSetupComplete.java index fc50c7ef3b9..516487a0e6d 100644 --- a/src/main/java/com/google/genai/types/LiveServerSetupComplete.java +++ b/src/main/java/com/google/genai/types/LiveServerSetupComplete.java @@ -34,6 +34,14 @@ public abstract class LiveServerSetupComplete extends JsonSerializable { @JsonProperty("sessionId") public abstract Optional sessionId(); + /** + * Signature of the verified consent audio. This is populated when the request has a + * ReplicatedVoiceConfig with consent_audio set, if the consent verification was successful. This + * may be used in a subsequent request instead of the consent_audio to verify the same consent. + */ + @JsonProperty("voiceConsentSignature") + public abstract Optional voiceConsentSignature(); + /** Instantiates a builder for LiveServerSetupComplete. */ @ExcludeFromGeneratedCoverageReport public static Builder builder() { @@ -70,6 +78,41 @@ public Builder clearSessionId() { return sessionId(Optional.empty()); } + /** + * Setter for voiceConsentSignature. + * + *

voiceConsentSignature: Signature of the verified consent audio. This is populated when the + * request has a ReplicatedVoiceConfig with consent_audio set, if the consent verification was + * successful. This may be used in a subsequent request instead of the consent_audio to verify + * the same consent. + */ + @JsonProperty("voiceConsentSignature") + public abstract Builder voiceConsentSignature(VoiceConsentSignature voiceConsentSignature); + + /** + * Setter for voiceConsentSignature builder. + * + *

voiceConsentSignature: Signature of the verified consent audio. This is populated when the + * request has a ReplicatedVoiceConfig with consent_audio set, if the consent verification was + * successful. This may be used in a subsequent request instead of the consent_audio to verify + * the same consent. + */ + @CanIgnoreReturnValue + public Builder voiceConsentSignature( + VoiceConsentSignature.Builder voiceConsentSignatureBuilder) { + return voiceConsentSignature(voiceConsentSignatureBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder voiceConsentSignature(Optional voiceConsentSignature); + + /** Clears the value of voiceConsentSignature field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearVoiceConsentSignature() { + return voiceConsentSignature(Optional.empty()); + } + public abstract LiveServerSetupComplete build(); } diff --git a/src/main/java/com/google/genai/types/ReplicatedVoiceConfig.java b/src/main/java/com/google/genai/types/ReplicatedVoiceConfig.java index 64ce622b96b..7a277091a06 100644 --- a/src/main/java/com/google/genai/types/ReplicatedVoiceConfig.java +++ b/src/main/java/com/google/genai/types/ReplicatedVoiceConfig.java @@ -41,6 +41,22 @@ public abstract class ReplicatedVoiceConfig extends JsonSerializable { @JsonProperty("voiceSampleAudio") public abstract Optional voiceSampleAudio(); + /** + * Recorded consent verifying ownership of the voice. This represents 16-bit signed little-endian + * wav data, with a 24kHz sampling rate. + */ + @JsonProperty("consentAudio") + public abstract Optional consentAudio(); + + /** + * Signature of a previously verified consent audio. This should be populated with a signature + * generated by the server for a previous request containing the consent_audio field. When + * provided, the signature is verified instead of the consent_audio field to reduce latency. + * Requests will fail if the signature is invalid or expired. + */ + @JsonProperty("voiceConsentSignature") + public abstract Optional voiceConsentSignature(); + /** Instantiates a builder for ReplicatedVoiceConfig. */ @ExcludeFromGeneratedCoverageReport public static Builder builder() { @@ -97,6 +113,60 @@ public Builder clearVoiceSampleAudio() { return voiceSampleAudio(Optional.empty()); } + /** + * Setter for consentAudio. + * + *

consentAudio: Recorded consent verifying ownership of the voice. This represents 16-bit + * signed little-endian wav data, with a 24kHz sampling rate. + */ + @JsonProperty("consentAudio") + public abstract Builder consentAudio(byte[] consentAudio); + + @ExcludeFromGeneratedCoverageReport + abstract Builder consentAudio(Optional consentAudio); + + /** Clears the value of consentAudio field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearConsentAudio() { + return consentAudio(Optional.empty()); + } + + /** + * Setter for voiceConsentSignature. + * + *

voiceConsentSignature: Signature of a previously verified consent audio. This should be + * populated with a signature generated by the server for a previous request containing the + * consent_audio field. When provided, the signature is verified instead of the consent_audio + * field to reduce latency. Requests will fail if the signature is invalid or expired. + */ + @JsonProperty("voiceConsentSignature") + public abstract Builder voiceConsentSignature(VoiceConsentSignature voiceConsentSignature); + + /** + * Setter for voiceConsentSignature builder. + * + *

voiceConsentSignature: Signature of a previously verified consent audio. This should be + * populated with a signature generated by the server for a previous request containing the + * consent_audio field. When provided, the signature is verified instead of the consent_audio + * field to reduce latency. Requests will fail if the signature is invalid or expired. + */ + @CanIgnoreReturnValue + public Builder voiceConsentSignature( + VoiceConsentSignature.Builder voiceConsentSignatureBuilder) { + return voiceConsentSignature(voiceConsentSignatureBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder voiceConsentSignature(Optional voiceConsentSignature); + + /** Clears the value of voiceConsentSignature field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearVoiceConsentSignature() { + return voiceConsentSignature(Optional.empty()); + } + public abstract ReplicatedVoiceConfig build(); } diff --git a/src/main/java/com/google/genai/types/VoiceConsentSignature.java b/src/main/java/com/google/genai/types/VoiceConsentSignature.java new file mode 100644 index 00000000000..216994504ac --- /dev/null +++ b/src/main/java/com/google/genai/types/VoiceConsentSignature.java @@ -0,0 +1,81 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Auto-generated code. Do not edit. + +package com.google.genai.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import java.util.Optional; + +/** The signature of the voice consent check. */ +@AutoValue +@JsonDeserialize(builder = VoiceConsentSignature.Builder.class) +public abstract class VoiceConsentSignature extends JsonSerializable { + /** The signature string. */ + @JsonProperty("signature") + public abstract Optional signature(); + + /** Instantiates a builder for VoiceConsentSignature. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_VoiceConsentSignature.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for VoiceConsentSignature. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `VoiceConsentSignature.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_VoiceConsentSignature.Builder(); + } + + /** + * Setter for signature. + * + *

signature: The signature string. + */ + @JsonProperty("signature") + public abstract Builder signature(String signature); + + @ExcludeFromGeneratedCoverageReport + abstract Builder signature(Optional signature); + + /** Clears the value of signature field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSignature() { + return signature(Optional.empty()); + } + + public abstract VoiceConsentSignature build(); + } + + /** Deserializes a JSON string to a VoiceConsentSignature object. */ + @ExcludeFromGeneratedCoverageReport + public static VoiceConsentSignature fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, VoiceConsentSignature.class); + } +} diff --git a/src/test/java/com/google/genai/AsyncLiveTest.java b/src/test/java/com/google/genai/AsyncLiveTest.java index 01907b1df5f..106b4eeab16 100644 --- a/src/test/java/com/google/genai/AsyncLiveTest.java +++ b/src/test/java/com/google/genai/AsyncLiveTest.java @@ -26,8 +26,10 @@ import com.google.genai.types.HttpOptions; import java.lang.reflect.Method; import java.net.URI; +import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -118,4 +120,25 @@ public void testGetWebSocketHeaders_GoogleAiEphemeralToken() throws Exception { assertEquals("Token auth_tokens/ephemeral-token", headers.get("Authorization")); } + @Test + public void testOnMessage_PopulatesSetupCompleteWithVoiceConsent() throws Exception { + CompletableFuture future = new CompletableFuture<>(); + URI uri = new URI("wss://test"); + Map headers = new HashMap<>(); + String setupRequest = "{}"; + + AsyncLive.GenAiWebSocketClient client = + new AsyncLive.GenAiWebSocketClient(uri, headers, setupRequest, future, apiClient); + + String message = + "{\"setupComplete\":{\"voiceConsentSignature\":{\"signature\":\"test_sig\"}}}"; + + client.onMessage(message); + + AsyncSession session = future.get(); + assertTrue(session != null); + assertTrue(session.setupComplete() != null); + assertTrue(session.setupComplete().voiceConsentSignature().isPresent()); + assertEquals("test_sig", session.setupComplete().voiceConsentSignature().get().signature().get()); + } }