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 4771687575c..2546d22296a 100644 --- a/src/main/java/com/google/genai/LiveConverters.java +++ b/src/main/java/com/google/genai/LiveConverters.java @@ -1828,6 +1828,15 @@ ObjectNode replicatedVoiceConfigToVertex(JsonNode fromObject, ObjectNode parentO Common.getValueByPath(fromObject, new String[] {"voiceSampleAudio"})); } + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"consentAudio"}))) { + throw new IllegalArgumentException("consentAudio parameter is not supported in Vertex AI."); + } + + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"voiceConsentSignature"}))) { + throw new IllegalArgumentException( + "voiceConsentSignature parameter is not supported in Vertex AI."); + } + return toObject; } diff --git a/src/main/java/com/google/genai/Models.java b/src/main/java/com/google/genai/Models.java index 9fe79592d40..ebe30117cfe 100644 --- a/src/main/java/com/google/genai/Models.java +++ b/src/main/java/com/google/genai/Models.java @@ -4539,6 +4539,15 @@ ObjectNode replicatedVoiceConfigToVertex( Common.getValueByPath(fromObject, new String[] {"voiceSampleAudio"})); } + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"consentAudio"}))) { + throw new IllegalArgumentException("consentAudio parameter is not supported in Vertex AI."); + } + + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"voiceConsentSignature"}))) { + throw new IllegalArgumentException( + "voiceConsentSignature parameter is not supported in Vertex AI."); + } + return toObject; } diff --git a/src/main/java/com/google/genai/Tunings.java b/src/main/java/com/google/genai/Tunings.java index 1b66e1dafbf..5979255220f 100644 --- a/src/main/java/com/google/genai/Tunings.java +++ b/src/main/java/com/google/genai/Tunings.java @@ -1257,6 +1257,15 @@ ObjectNode replicatedVoiceConfigToVertex( Common.getValueByPath(fromObject, new String[] {"voiceSampleAudio"})); } + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"consentAudio"}))) { + throw new IllegalArgumentException("consentAudio parameter is not supported in Vertex AI."); + } + + if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"voiceConsentSignature"}))) { + throw new IllegalArgumentException( + "voiceConsentSignature parameter is not supported in Vertex AI."); + } + return toObject; } 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()); + } }