From 12430b74e934bbbe65aa97dc1edcc7eb4ff77985 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 13:28:47 +0100 Subject: [PATCH 01/14] chore: initialize SDK regeneration branch From bd300aca11f8dbe6fff9e75baf787f6d4ef9f769 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 13:29:32 +0100 Subject: [PATCH 02/14] chore: unfreeze files pending regen --- .fernignore | 48 +- .../com/deepgram/core/ClientOptions.java.bak | 340 ++++++ .../ReconnectingWebSocketListener.java.bak | 590 +++++++++ .../v1/types/AgentV1AgentAudioDone.java.bak | 85 ++ .../v1/types/AgentV1ForceEndTurn.java.bak | 85 ++ .../agent/v1/types/AgentV1KeepAlive.java.bak | 85 ++ .../v1/types/AgentV1ListenUpdated.java.bak | 85 ++ .../v1/types/AgentV1PromptUpdated.java.bak | 85 ++ ...ettingsAgentContextListenProvider.java.bak | 226 ++++ ...gentV1SettingsAgentListenProvider.java.bak | 226 ++++ .../v1/types/AgentV1SettingsApplied.java.bak | 85 ++ .../v1/types/AgentV1SpeakUpdated.java.bak | 85 ++ .../v1/types/AgentV1ThinkUpdated.java.bak | 85 ++ ...AgentV1UpdateListenListenProvider.java.bak | 226 ++++ .../types/AgentV1UserStartedSpeaking.java.bak | 85 ++ .../v1/websocket/V1WebSocketClient.java.bak | 868 +++++++++++++ .../v1/websocket/V1WebSocketClient.java.bak | 545 +++++++++ .../v2/types/ListenV2CloseStream.java.bak | 85 ++ .../v2/types/ListenV2ForceEndTurn.java.bak | 85 ++ .../v2/websocket/V2WebSocketClient.java.bak | 532 ++++++++ .../v1/websocket/V1WebSocketClient.java.bak | 458 +++++++ .../speak/v2/types/SpeakV2Close.java.bak | 85 ++ .../speak/v2/types/SpeakV2Flush.java.bak | 85 ++ .../v2/websocket/V2WebSocketClient.java.bak | 621 ++++++++++ .../com/deepgram/types/DeepgramModel.java.bak | 1073 +++++++++++++++++ 25 files changed, 6834 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/deepgram/core/ClientOptions.java.bak create mode 100644 src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak create mode 100644 src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak create mode 100644 src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak create mode 100644 src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak create mode 100644 src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak create mode 100644 src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak create mode 100644 src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak create mode 100644 src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak create mode 100644 src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak create mode 100644 src/main/java/com/deepgram/types/DeepgramModel.java.bak diff --git a/.fernignore b/.fernignore index ee10818..babb6bf 100644 --- a/.fernignore +++ b/.fernignore @@ -15,7 +15,7 @@ src/main/java/com/deepgram/AsyncDeepgramClientBuilder.java # with // x-release-please-version comments for automated version bumps. # Fern regen overwrites these with incorrect SDK names and strips the markers. # Fern 4.21.0 owns the OkHttp lifecycle behavior; retain it while reapplying only these headers. -src/main/java/com/deepgram/core/ClientOptions.java +src/main/java/com/deepgram/core/ClientOptions.java.bak # Transport abstraction (pluggable transport for SageMaker, etc.) src/main/java/com/deepgram/core/transport/ @@ -23,7 +23,7 @@ src/main/java/com/deepgram/core/transport/ # Bug fixes for maxRetries(0) semantics ("connect once, don't retry") and a # configurable connectionTimeoutMs on ReconnectOptions (was hardcoded 4000ms). # Pull this back out once the fixes are upstreamed into the Fern generator. -src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak # Forward-compat patch: Fern's generated dispatcher routes any unrecognized message # type to the error handler ("Update your SDK version..."), which makes benign new @@ -33,8 +33,8 @@ src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java # NOTE: the listen v2 and speak v2 clients below also carry the streaming query-param patches # (multi-value serialization + the additionalProperties escape hatch) documented lower down — # keep them frozen until those fixes are upstreamed too. -src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java -src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java +src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak +src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak # Streaming query-param patches on the generated WS connect() builders. Two fixes: # (1) Multi-value serialization: array-valued params (listen: keyterm, keywords, replace, search, @@ -48,13 +48,13 @@ src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java # listen v2 and speak v2 clients are frozen above; freeze the listen v1 and speak v1 clients here too # so the fixes survive regen. Re-apply after regen; unfreeze once the generator emits array params as # repeats and serializes additionalProperties on the WS connect path (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java -src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java +src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak +src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak # Close is safe before connect in every generated WebSocket client. The listener is created only # during connect(), so disconnect() must tolerate a null listener for try-with-resources clients. # Remove these patches once Fern emits the null guard. -src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java +src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak # Restores the FLUX_RENEE_EN constant that generator 4.18.0 dropped. The voice is live: # POST /v2/speak?model=flux-renee-en returns 200 with valid audio, and the name resolves in the @@ -66,25 +66,25 @@ src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java # changes -- 4.18.0 alone added 25 constants. On the next regen do NOT restore the .bak wholesale: # diff it against the newly generated file, carry forward every new voice, and re-apply only the # FLUX_RENEE_EN touchpoints (constant, Value enum, visit() case, valueOf() case, Visitor method). -src/main/java/com/deepgram/types/DeepgramModel.java +src/main/java/com/deepgram/types/DeepgramModel.java.bak # Manual equals/hashCode contract fix: Fern generates equals() (all instances equal) but no # hashCode() for fields-less message types, violating the Object contract. We add a consistent # hashCode() to each. Unfreeze and drop these patches once the generator emits a matching # equals/hashCode pair for fields-less types (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java -src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java -src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java -src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java +src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak +src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak +src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak +src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak # Union default-variant fix (STOPGAP). The agent listen-provider unions declare `version` as an # optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits). Fern @@ -93,9 +93,9 @@ src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java # {"provider":null}, silently dropping the caller's provider on the wire. Patched to # defaultImpl = V2Value on each. Guarded by AgentSettingsProviderDefaultTest. Unfreeze and drop once # the generator stops defaulting unions to the empty _UnknownValue (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak # Build and project configuration build.gradle diff --git a/src/main/java/com/deepgram/core/ClientOptions.java.bak b/src/main/java/com/deepgram/core/ClientOptions.java.bak new file mode 100644 index 0000000..5329810 --- /dev/null +++ b/src/main/java/com/deepgram/core/ClientOptions.java.bak @@ -0,0 +1,340 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private final boolean ownsHttpClient; + + private final int timeout; + + private final int maxRetries; + + private final Optional initialRetryDelayMillis; + + private final Optional maxRetryDelayMillis; + + private final Optional retryJitterFactor; + + private final Optional webSocketFactory; + + private final Optional logging; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + boolean ownsHttpClient, + int timeout, + int maxRetries, + Optional initialRetryDelayMillis, + Optional maxRetryDelayMillis, + Optional retryJitterFactor, + Optional webSocketFactory, + Optional logging) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("User-Agent", "com.deepgram:deepgram-java-sdk/0.10.2"); // x-release-please-version + put("X-Fern-Language", "JAVA"); + put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); + put("X-Fern-SDK-Version", "0.10.2"); // x-release-please-version + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.ownsHttpClient = ownsHttpClient; + this.timeout = timeout; + this.maxRetries = maxRetries; + this.initialRetryDelayMillis = initialRetryDelayMillis; + this.maxRetryDelayMillis = maxRetryDelayMillis; + this.retryJitterFactor = retryJitterFactor; + this.webSocketFactory = webSocketFactory; + this.logging = logging; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public int maxRetries() { + return this.maxRetries; + } + + public Optional initialRetryDelayMillis() { + return this.initialRetryDelayMillis; + } + + public Optional maxRetryDelayMillis() { + return this.maxRetryDelayMillis; + } + + public Optional retryJitterFactor() { + return this.retryJitterFactor; + } + + /** + * Releases resources owned by this client. Only shuts down the underlying OkHttpClient's + * dispatcher executor and evicts its connection pool when this client created that + * OkHttpClient itself; an OkHttpClient supplied via httpClient is left running, since the + * caller owns its lifecycle. + *

+ * In-flight calls are not cancelled or awaited. Later asynchronous requests fail when the + * dispatcher rejects work, while synchronous requests may still run. Options derived from this + * one via {@code Builder.from(...)} share the same dispatcher and connection pool, so closing + * either releases them for both. Calling this method more than once has no further effect. + */ + public void close() { + if (!this.ownsHttpClient) { + return; + } + this.httpClient.dispatcher().executorService().shutdown(); + this.httpClient.connectionPool().evictAll(); + } + + public Optional webSocketFactory() { + return this.webSocketFactory; + } + + public Optional logging() { + return this.logging; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private int maxRetries = 2; + + private Optional initialRetryDelayMillis = Optional.empty(); + + private Optional maxRetryDelayMillis = Optional.empty(); + + private Optional retryJitterFactor = Optional.empty(); + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + private boolean ownsHttpClient = true; + + private Optional logging = Optional.empty(); + + private Optional webSocketFactory = Optional.empty(); + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + if (value != null) { + this.headers.put(key, value); + } + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + /** + * Override the initial delay (in milliseconds) used for exponential backoff between retries. Defaults to 1000 milliseconds. + */ + public Builder initialRetryDelayMillis(long initialRetryDelayMillis) { + this.initialRetryDelayMillis = Optional.of(initialRetryDelayMillis); + return this; + } + + /** + * Override the maximum delay (in milliseconds) between retries. Defaults to 60000 milliseconds. + */ + public Builder maxRetryDelayMillis(long maxRetryDelayMillis) { + this.maxRetryDelayMillis = Optional.of(maxRetryDelayMillis); + return this; + } + + /** + * Override the jitter factor (between 0 and 1) applied to retry delays. Defaults to 0.2. + */ + public Builder retryJitterFactor(double retryJitterFactor) { + this.retryJitterFactor = Optional.of(retryJitterFactor); + return this; + } + + /** + * Sets the underlying OkHttp client. The caller retains ownership of its lifecycle: + * close() will not shut down its dispatcher executor or evict its connection pool. + */ + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + this.ownsHttpClient = httpClient == null; + return this; + } + + /** + * Set a custom WebSocketFactory for creating WebSocket connections. + */ + public Builder webSocketFactory(WebSocketFactory webSocketFactory) { + this.webSocketFactory = Optional.of(webSocketFactory); + return this; + } + + /** + * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. + */ + public Builder logging(LogConfig logging) { + this.logging = Optional.of(logging); + return this; + } + + public ClientOptions build() { + OkHttpClient.Builder httpClientBuilder = + this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); + + if (this.httpClient != null) { + timeout.ifPresent(timeout -> httpClientBuilder + .callTimeout(timeout, TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS)); + } else { + httpClientBuilder + .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .addInterceptor(new RetryInterceptor( + this.maxRetries, + this.initialRetryDelayMillis, + this.maxRetryDelayMillis, + this.retryJitterFactor)); + } + + Logger logger = Logger.from(this.logging); + httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); + httpClientBuilder.addInterceptor(new ResponseDecompressionInterceptor()); + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions( + environment, + headers, + headerSuppliers, + httpClient, + this.ownsHttpClient, + this.timeout.get(), + this.maxRetries, + this.initialRetryDelayMillis, + this.maxRetryDelayMillis, + this.retryJitterFactor, + this.webSocketFactory, + this.logging); + } + + /** + * Create a new Builder initialized with values from an existing ClientOptions + */ + public static Builder from(ClientOptions clientOptions) { + Builder builder = new Builder(); + builder.environment = clientOptions.environment(); + builder.timeout = Optional.of(clientOptions.timeout(null)); + builder.httpClient = clientOptions.httpClient(); + builder.ownsHttpClient = clientOptions.ownsHttpClient; + builder.headers.putAll(clientOptions.headers); + builder.headerSuppliers.putAll(clientOptions.headerSuppliers); + builder.maxRetries = clientOptions.maxRetries(); + builder.initialRetryDelayMillis = clientOptions.initialRetryDelayMillis(); + builder.maxRetryDelayMillis = clientOptions.maxRetryDelayMillis(); + builder.retryJitterFactor = clientOptions.retryJitterFactor(); + builder.logging = clientOptions.logging(); + return builder; + } + } +} diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak new file mode 100644 index 0000000..7f6d9df --- /dev/null +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak @@ -0,0 +1,590 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.core; + +import static java.util.concurrent.TimeUnit.*; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Supplier; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okio.ByteString; + +/** + * WebSocketListener with automatic reconnection, exponential backoff, and message queuing. + * Provides production-ready resilience for WebSocket connections. + */ +public abstract class ReconnectingWebSocketListener extends WebSocketListener { + // A single volatile reference keeps an override internally consistent. + private volatile ReconnectOptions activeOptions; + + private final int maxEnqueuedMessages; + + private final AtomicInteger retryCount = new AtomicInteger(0); + + private final AtomicBoolean connectLock = new AtomicBoolean(false); + + private final AtomicBoolean shouldReconnect = new AtomicBoolean(true); + + protected volatile WebSocket webSocket; + + private volatile long connectionEstablishedTime = 0L; + + private final ConcurrentLinkedQueue messageQueue = new ConcurrentLinkedQueue<>(); + + private final ConcurrentLinkedQueue binaryMessageQueue = new ConcurrentLinkedQueue<>(); + + private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(); + + private final Supplier connectionSupplier; + + /** + * Creates a new reconnecting WebSocket listener. + * + * @param options Reconnection configuration options + * @param connectionSupplier Supplier that creates new WebSocket connections + */ + public ReconnectingWebSocketListener( + ReconnectingWebSocketListener.ReconnectOptions options, Supplier connectionSupplier) { + this.activeOptions = options; + this.maxEnqueuedMessages = options.maxEnqueuedMessages; + this.connectionSupplier = connectionSupplier; + } + + /** + * Replaces option-derived reconnect settings without rebuilding generated clients. + * + * @param options replacement options; {@code null} is a no-op + */ + public void applyOptionsOverride(ReconnectOptions options) { + if (options != null) { + this.activeOptions = options; + } + } + + /** + * Initiates a WebSocket connection with automatic reconnection enabled. + * + * Connection behavior: + * - Times out after {@code ReconnectOptions.connectionTimeoutMs} (default 4000ms) + * - Thread-safe via atomic lock (returns immediately if connection in progress) + * - {@code maxRetries} counts retries only; {@code maxRetries(0)} means connect once, do not retry + * + * Error handling: + * - TimeoutException: Includes retry attempt context + * - InterruptedException: Preserves thread interruption status + * - ExecutionException: Extracts actual cause and adds context + */ + public void connect() { + if (!connectLock.compareAndSet(false, true)) { + return; + } + ReconnectOptions options = this.activeOptions; + if (retryCount.get() > options.maxRetries) { + connectLock.set(false); + return; + } + try { + CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); + try { + webSocket = connectionFuture.get(options.connectionTimeoutMs, MILLISECONDS); + } catch (TimeoutException e) { + connectionFuture.cancel(true); + TimeoutException timeoutError = + new TimeoutException("WebSocket connection timeout after " + options.connectionTimeoutMs + " milliseconds" + + (retryCount.get() > 0 + ? " (retry attempt #" + retryCount.get() + ")" + : " (initial connection attempt)")); + onWebSocketFailure(null, timeoutError, null); + if (shouldReconnect.get()) { + scheduleReconnect(); + } + } catch (InterruptedException e) { + connectionFuture.cancel(true); + Thread.currentThread().interrupt(); + InterruptedException interruptError = new InterruptedException("WebSocket connection interrupted" + + (retryCount.get() > 0 + ? " during retry attempt #" + retryCount.get() + : " during initial connection")); + interruptError.initCause(e); + onWebSocketFailure(null, interruptError, null); + } catch (ExecutionException e) { + Throwable cause = e.getCause() != null ? e.getCause() : e; + String context = retryCount.get() > 0 + ? "WebSocket connection failed during retry attempt #" + retryCount.get() + : "WebSocket connection failed during initial attempt"; + RuntimeException wrappedException = new RuntimeException( + context + ": " + cause.getClass().getSimpleName() + ": " + cause.getMessage()); + wrappedException.initCause(cause); + onWebSocketFailure(null, wrappedException, null); + if (shouldReconnect.get()) { + scheduleReconnect(); + } + } + } finally { + connectLock.set(false); + } + } + + /** + * Disconnects the WebSocket and disables automatic reconnection. + * + * This method: + * - Disables automatic reconnection + * - Clears queued messages to prevent stale data + * - Closes the WebSocket with standard close code 1000 + * - Properly shuts down the reconnect executor to prevent thread leaks + * - Waits up to 5 seconds for executor termination + */ + public void disconnect() { + shouldReconnect.set(false); + messageQueue.clear(); + binaryMessageQueue.clear(); + if (webSocket != null) { + webSocket.close(1000, "Client disconnecting"); + } + reconnectExecutor.shutdown(); + try { + if (!reconnectExecutor.awaitTermination(5, SECONDS)) { + reconnectExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + reconnectExecutor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + /** + * Sends a message or queues it if not connected. + * + * Thread-safe: Synchronized to prevent race conditions with flushMessageQueue(). + * + * Behavior: + * - If connected: Attempts direct send, queues if buffer full + * - If disconnected: Queues message up to maxEnqueuedMessages limit + * - If queue full: Message is dropped + * + * @param message The message to send + * @return true if sent immediately, false if queued or dropped + */ + public synchronized boolean send(String message) { + return send(message, null); + } + + /** + * Sends a message or queues it if not connected, exposing the accepting socket. + * + * Behaves like {@link #send(String)}, but additionally invokes {@code onSent} with the + * WebSocket that accepted the message. The callback is only invoked when the message was + * sent directly, never when it was queued or dropped. This lets callers associate a + * protocol-level message with the specific connection it was delivered on, e.g. to decide + * in {@link #shouldReconnectAfterClose(WebSocket, int, String)} whether a later close of that same + * connection was expected. + * + * @param message The message to send + * @param onSent Callback receiving the WebSocket that accepted the message, or null + * @return true if sent immediately, false if queued or dropped + */ + public synchronized boolean send(String message, Consumer onSent) { + WebSocket ws = webSocket; + if (ws != null) { + boolean sent = ws.send(message); + if (sent && onSent != null) { + onSent.accept(ws); + } + if (!sent && messageQueue.size() < maxEnqueuedMessages) { + messageQueue.offer(message); + return false; + } + return sent; + } else { + if (messageQueue.size() < maxEnqueuedMessages) { + messageQueue.offer(message); + return false; + } + return false; + } + } + + /** + * Sends binary data or queues it if not connected. + * + * Thread-safe: Synchronized to prevent race conditions with flushMessageQueue(). + * + * Behavior: + * - If connected: Attempts direct send, queues if buffer full + * - If disconnected: Queues data up to maxEnqueuedMessages limit + * - If queue full: Data is dropped + * + * @param data The binary data to send + * @return true if sent immediately, false if queued or dropped + */ + public synchronized boolean sendBinary(ByteString data) { + return sendBinary(data, null); + } + + /** + * Sends binary data or queues it if not connected, exposing the accepting socket. + * + * Behaves like {@link #sendBinary(ByteString)}, but additionally invokes {@code onSent} with + * the WebSocket that accepted the data. The callback is only invoked when the data was sent + * directly, never when it was queued or dropped. + * + * @param data The binary data to send + * @param onSent Callback receiving the WebSocket that accepted the data, or null + * @return true if sent immediately, false if queued or dropped + */ + public synchronized boolean sendBinary(ByteString data, Consumer onSent) { + WebSocket ws = webSocket; + if (ws != null) { + boolean sent = ws.send(data); + if (sent && onSent != null) { + onSent.accept(ws); + } + if (!sent && binaryMessageQueue.size() < maxEnqueuedMessages) { + binaryMessageQueue.offer(data); + return false; + } + return sent; + } else { + if (binaryMessageQueue.size() < maxEnqueuedMessages) { + binaryMessageQueue.offer(data); + return false; + } + return false; + } + } + + /** + * Gets the current WebSocket instance. + * Thread-safe method to access the WebSocket connection. + * @return the WebSocket or null if not connected + */ + public WebSocket getWebSocket() { + return webSocket; + } + + /** + * @hidden + */ + @Override + public void onOpen(WebSocket webSocket, Response response) { + this.webSocket = webSocket; + connectionEstablishedTime = System.currentTimeMillis(); + retryCount.set(0); + flushMessageQueue(); + onWebSocketOpen(webSocket, response); + } + + @Override + public void onMessage(WebSocket webSocket, String text) { + onWebSocketMessage(webSocket, text); + } + + @Override + public void onMessage(WebSocket webSocket, ByteString bytes) { + onWebSocketBinaryMessage(webSocket, bytes); + } + + /** + * @hidden + */ + @Override + public void onFailure(WebSocket webSocket, Throwable t, Response response) { + this.webSocket = null; + long uptime = 0L; + if (connectionEstablishedTime > 0) { + uptime = System.currentTimeMillis() - connectionEstablishedTime; + if (uptime >= 5000) { + retryCount.set(0); + } + } + connectionEstablishedTime = 0L; + Throwable enhancedError = t; + if (t != null) { + String errorContext = "WebSocket connection failed"; + if (uptime > 0) { + errorContext += " after " + (uptime / 1000) + " seconds"; + } + if (response != null) { + errorContext += " with HTTP " + response.code() + " " + response.message(); + } + enhancedError = + new RuntimeException(errorContext + ": " + t.getClass().getSimpleName() + ": " + t.getMessage()); + enhancedError.initCause(t); + } + onWebSocketFailure(webSocket, enhancedError, response); + if (shouldReconnect.get()) { + scheduleReconnect(); + } + } + + /** + * Acknowledges a peer-initiated close so OkHttp can complete the close handshake and + * invoke {@link #onClosed(WebSocket, int, String)}. + * + * Code 1005 is a local "no status received" sentinel reported when the peer sent an empty + * close frame. It is not a valid code to put on the wire, so it is acknowledged with + * the normal closure code 1000 instead. + * + * @hidden + */ + @Override + public void onClosing(WebSocket webSocket, int code, String reason) { + webSocket.close(code == 1005 ? 1000 : code, reason); + } + + /** + * @hidden + */ + @Override + public void onClosed(WebSocket webSocket, int code, String reason) { + this.webSocket = null; + if (connectionEstablishedTime > 0) { + long uptime = System.currentTimeMillis() - connectionEstablishedTime; + if (uptime >= 5000) { + retryCount.set(0); + } + } + connectionEstablishedTime = 0L; + onWebSocketClosed(webSocket, code, reason); + if (shouldReconnect.get() && shouldReconnectAfterClose(webSocket, code, reason)) { + scheduleReconnect(); + } + } + + /** + * Decides whether a completed close handshake should trigger a reconnect. + * + * Only consulted when reconnection has not been disabled via {@link #disconnect()}. + * The default treats normal closure (1000) as terminal and reconnects on any + * other code. Subclasses can override this when protocol context establishes that a + * particular close is terminal, for example a no-status close following a message that + * ends the stream. + * + * @param webSocket The WebSocket that was closed + * @param code The close status code reported by OkHttp + * @param reason The close reason sent by the peer, or an empty string + * @return true to schedule a reconnect, false to stay disconnected + */ + protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, String reason) { + return code != 1000; + } + + /** + * Calculates the next reconnection delay using exponential backoff. + * + * Uses 0-based retry count where: + * - 0 = initial connection (not used by this method) + * - 1 = first retry (returns minReconnectionDelayMs) + * - 2+ = exponential backoff up to maxReconnectionDelayMs + */ + private long getNextDelay() { + ReconnectOptions options = this.activeOptions; + if (retryCount.get() == 1) { + return options.minReconnectionDelayMs; + } + long delay = (long) + (options.minReconnectionDelayMs * Math.pow(options.reconnectionDelayGrowFactor, retryCount.get() - 1)); + return Math.min(delay, options.maxReconnectionDelayMs); + } + + /** + * Schedules a reconnection attempt with appropriate delay. + * Increments retry count and uses exponential backoff. + */ + private void scheduleReconnect() { + retryCount.incrementAndGet(); + long delay = getNextDelay(); + reconnectExecutor.schedule(this::connect, delay, MILLISECONDS); + } + + /** + * Sends all queued messages after reconnection. + * + * Thread-safe: Synchronized to prevent race conditions with send() method. + * + * Algorithm: + * 1. Drains queue into temporary list to avoid holding lock during sends + * 2. Attempts to send each message in order + * 3. If any send fails, re-queues that message and all subsequent messages + * 4. Preserves message ordering during re-queueing + * 5. Repeats for binary message queue + */ + private synchronized void flushMessageQueue() { + WebSocket ws = webSocket; + if (ws != null) { + ArrayList tempQueue = new ArrayList<>(); + String message; + while ((message = messageQueue.poll()) != null) { + tempQueue.add(message); + } + for (int i = 0; i < tempQueue.size(); i++) { + if (!ws.send(tempQueue.get(i))) { + for (int j = i; j < tempQueue.size(); j++) { + messageQueue.offer(tempQueue.get(j)); + } + break; + } + } + ArrayList tempBinaryQueue = new ArrayList<>(); + ByteString binaryMsg; + while ((binaryMsg = binaryMessageQueue.poll()) != null) { + tempBinaryQueue.add(binaryMsg); + } + for (int i = 0; i < tempBinaryQueue.size(); i++) { + if (!ws.send(tempBinaryQueue.get(i))) { + for (int j = i; j < tempBinaryQueue.size(); j++) { + binaryMessageQueue.offer(tempBinaryQueue.get(j)); + } + break; + } + } + } + } + + protected abstract void onWebSocketOpen(WebSocket webSocket, Response response); + + protected abstract void onWebSocketMessage(WebSocket webSocket, String text); + + protected abstract void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes); + + protected abstract void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response); + + protected abstract void onWebSocketClosed(WebSocket webSocket, int code, String reason); + + /** + * Configuration options for automatic reconnection. + */ + public static final class ReconnectOptions { + public final long minReconnectionDelayMs; + + public final long maxReconnectionDelayMs; + + public final double reconnectionDelayGrowFactor; + + public final int maxRetries; + + public final int maxEnqueuedMessages; + + public final long connectionTimeoutMs; + + private ReconnectOptions(Builder builder) { + this.minReconnectionDelayMs = builder.minReconnectionDelayMs; + this.maxReconnectionDelayMs = builder.maxReconnectionDelayMs; + this.reconnectionDelayGrowFactor = builder.reconnectionDelayGrowFactor; + this.maxRetries = builder.maxRetries; + this.maxEnqueuedMessages = builder.maxEnqueuedMessages; + this.connectionTimeoutMs = builder.connectionTimeoutMs; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private long minReconnectionDelayMs; + + private long maxReconnectionDelayMs; + + private double reconnectionDelayGrowFactor; + + private int maxRetries; + + private int maxEnqueuedMessages; + + private long connectionTimeoutMs; + + public Builder() { + this.minReconnectionDelayMs = 1000; + this.maxReconnectionDelayMs = 10000; + this.reconnectionDelayGrowFactor = 1.3; + this.maxRetries = 2147483647; + this.maxEnqueuedMessages = 1000; + this.connectionTimeoutMs = 4000; + } + + public Builder minReconnectionDelayMs(long minReconnectionDelayMs) { + this.minReconnectionDelayMs = minReconnectionDelayMs; + return this; + } + + public Builder maxReconnectionDelayMs(long maxReconnectionDelayMs) { + this.maxReconnectionDelayMs = maxReconnectionDelayMs; + return this; + } + + public Builder reconnectionDelayGrowFactor(double reconnectionDelayGrowFactor) { + this.reconnectionDelayGrowFactor = reconnectionDelayGrowFactor; + return this; + } + + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { + this.maxEnqueuedMessages = maxEnqueuedMessages; + return this; + } + + /** + * Sets the per-attempt connection timeout in milliseconds. Defaults to {@code 4000}. + */ + public Builder connectionTimeoutMs(long connectionTimeoutMs) { + this.connectionTimeoutMs = connectionTimeoutMs; + return this; + } + + /** + * Builds the ReconnectOptions with validation. + * + * Validates that: + * - All delay values are positive + * - minReconnectionDelayMs <= maxReconnectionDelayMs + * - reconnectionDelayGrowFactor >= 1.0 + * - maxRetries and maxEnqueuedMessages are non-negative + * + * @return The validated ReconnectOptions instance + * @throws IllegalArgumentException if configuration is invalid + */ + public ReconnectOptions build() { + if (minReconnectionDelayMs <= 0) { + throw new IllegalArgumentException("minReconnectionDelayMs must be positive"); + } + if (maxReconnectionDelayMs <= 0) { + throw new IllegalArgumentException("maxReconnectionDelayMs must be positive"); + } + if (minReconnectionDelayMs > maxReconnectionDelayMs) { + throw new IllegalArgumentException("minReconnectionDelayMs (" + minReconnectionDelayMs + + ") must not exceed maxReconnectionDelayMs (" + maxReconnectionDelayMs + ")"); + } + if (reconnectionDelayGrowFactor < 1.0) { + throw new IllegalArgumentException("reconnectionDelayGrowFactor must be >= 1.0"); + } + if (maxRetries < 0) { + throw new IllegalArgumentException("maxRetries must be non-negative"); + } + if (maxEnqueuedMessages < 0) { + throw new IllegalArgumentException("maxEnqueuedMessages must be non-negative"); + } + if (connectionTimeoutMs <= 0) { + throw new IllegalArgumentException("connectionTimeoutMs must be positive"); + } + return new ReconnectOptions(this); + } + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak new file mode 100644 index 0000000..b6ee900 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1AgentAudioDone.Builder.class) +public final class AgentV1AgentAudioDone { + private final Map additionalProperties; + + private AgentV1AgentAudioDone(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier indicating the agent has finished sending audio + */ + @JsonProperty("type") + public String getType() { + return "AgentAudioDone"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1AgentAudioDone; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1AgentAudioDone other) { + return this; + } + + public AgentV1AgentAudioDone build() { + return new AgentV1AgentAudioDone(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak new file mode 100644 index 0000000..11d0d8d --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1ForceEndTurn.Builder.class) +public final class AgentV1ForceEndTurn { + private final Map additionalProperties; + + private AgentV1ForceEndTurn(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for forcing the end of the current turn + */ + @JsonProperty("type") + public String getType() { + return "ForceEndTurn"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1ForceEndTurn; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1ForceEndTurn other) { + return this; + } + + public AgentV1ForceEndTurn build() { + return new AgentV1ForceEndTurn(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak new file mode 100644 index 0000000..588aad1 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1KeepAlive.Builder.class) +public final class AgentV1KeepAlive { + private final Map additionalProperties; + + private AgentV1KeepAlive(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "KeepAlive"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1KeepAlive; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1KeepAlive other) { + return this; + } + + public AgentV1KeepAlive build() { + return new AgentV1KeepAlive(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak new file mode 100644 index 0000000..3de75ec --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1ListenUpdated.Builder.class) +public final class AgentV1ListenUpdated { + private final Map additionalProperties; + + private AgentV1ListenUpdated(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for listen update confirmation + */ + @JsonProperty("type") + public String getType() { + return "ListenUpdated"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1ListenUpdated; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1ListenUpdated other) { + return this; + } + + public AgentV1ListenUpdated build() { + return new AgentV1ListenUpdated(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak new file mode 100644 index 0000000..2b2f794 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1PromptUpdated.Builder.class) +public final class AgentV1PromptUpdated { + private final Map additionalProperties; + + private AgentV1PromptUpdated(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for prompt update confirmation + */ + @JsonProperty("type") + public String getType() { + return "PromptUpdated"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1PromptUpdated; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1PromptUpdated other) { + return this; + } + + public AgentV1PromptUpdated build() { + return new AgentV1PromptUpdated(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak new file mode 100644 index 0000000..1c21ce7 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak @@ -0,0 +1,226 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.types.DeepgramListenProviderV1; +import com.deepgram.types.DeepgramListenProviderV2; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Objects; +import java.util.Optional; + +public final class AgentV1SettingsAgentContextListenProvider { + private final Value value; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + private AgentV1SettingsAgentContextListenProvider(Value value) { + this.value = value; + } + + public T visit(Visitor visitor) { + return value.visit(visitor); + } + + public static AgentV1SettingsAgentContextListenProvider v1(DeepgramListenProviderV1 value) { + return new AgentV1SettingsAgentContextListenProvider(new V1Value(value)); + } + + public static AgentV1SettingsAgentContextListenProvider v2(DeepgramListenProviderV2 value) { + return new AgentV1SettingsAgentContextListenProvider(new V2Value(value)); + } + + public boolean isV1() { + return value instanceof V1Value; + } + + public boolean isV2() { + return value instanceof V2Value; + } + + public boolean _isUnknown() { + return value instanceof _UnknownValue; + } + + public Optional getV1() { + if (isV1()) { + return Optional.of(((V1Value) value).value); + } + return Optional.empty(); + } + + public Optional getV2() { + if (isV2()) { + return Optional.of(((V2Value) value).value); + } + return Optional.empty(); + } + + public Optional _getUnknown() { + if (_isUnknown()) { + return Optional.of(((_UnknownValue) value).value); + } + return Optional.empty(); + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1SettingsAgentContextListenProvider + && value.equals(((AgentV1SettingsAgentContextListenProvider) other).value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return value.toString(); + } + + @JsonValue + private Value getValue() { + return this.value; + } + + public interface Visitor { + T visitV1(DeepgramListenProviderV1 v1); + + T visitV2(DeepgramListenProviderV2 v2); + + T _visitUnknown(Object unknownType); + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) + @JsonIgnoreProperties(ignoreUnknown = true) + private interface Value { + T visit(Visitor visitor); + } + + @JsonTypeName("v1") + @JsonIgnoreProperties("version") + private static final class V1Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV1 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V1Value() {} + + private V1Value(DeepgramListenProviderV1 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV1(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1Value && equalTo((V1Value) other); + } + + private boolean equalTo(V1Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentContextListenProvider{" + "value: " + value + "}"; + } + } + + @JsonTypeName("v2") + @JsonIgnoreProperties("version") + private static final class V2Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV2 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V2Value() {} + + private V2Value(DeepgramListenProviderV2 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV2(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V2Value && equalTo((V2Value) other); + } + + private boolean equalTo(V2Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentContextListenProvider{" + "value: " + value + "}"; + } + } + + @JsonIgnoreProperties("version") + private static final class _UnknownValue implements Value { + private String type; + + @JsonValue + private Object value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private _UnknownValue(@JsonProperty("value") Object value) {} + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor._visitUnknown(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof _UnknownValue && equalTo((_UnknownValue) other); + } + + private boolean equalTo(_UnknownValue other) { + return type.equals(other.type) && value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.type, this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentContextListenProvider{" + "type: " + type + ", value: " + value + "}"; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak new file mode 100644 index 0000000..06e47e6 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak @@ -0,0 +1,226 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.types.DeepgramListenProviderV1; +import com.deepgram.types.DeepgramListenProviderV2; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Objects; +import java.util.Optional; + +public final class AgentV1SettingsAgentListenProvider { + private final Value value; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + private AgentV1SettingsAgentListenProvider(Value value) { + this.value = value; + } + + public T visit(Visitor visitor) { + return value.visit(visitor); + } + + public static AgentV1SettingsAgentListenProvider v1(DeepgramListenProviderV1 value) { + return new AgentV1SettingsAgentListenProvider(new V1Value(value)); + } + + public static AgentV1SettingsAgentListenProvider v2(DeepgramListenProviderV2 value) { + return new AgentV1SettingsAgentListenProvider(new V2Value(value)); + } + + public boolean isV1() { + return value instanceof V1Value; + } + + public boolean isV2() { + return value instanceof V2Value; + } + + public boolean _isUnknown() { + return value instanceof _UnknownValue; + } + + public Optional getV1() { + if (isV1()) { + return Optional.of(((V1Value) value).value); + } + return Optional.empty(); + } + + public Optional getV2() { + if (isV2()) { + return Optional.of(((V2Value) value).value); + } + return Optional.empty(); + } + + public Optional _getUnknown() { + if (_isUnknown()) { + return Optional.of(((_UnknownValue) value).value); + } + return Optional.empty(); + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1SettingsAgentListenProvider + && value.equals(((AgentV1SettingsAgentListenProvider) other).value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return value.toString(); + } + + @JsonValue + private Value getValue() { + return this.value; + } + + public interface Visitor { + T visitV1(DeepgramListenProviderV1 v1); + + T visitV2(DeepgramListenProviderV2 v2); + + T _visitUnknown(Object unknownType); + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) + @JsonIgnoreProperties(ignoreUnknown = true) + private interface Value { + T visit(Visitor visitor); + } + + @JsonTypeName("v1") + @JsonIgnoreProperties("version") + private static final class V1Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV1 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V1Value() {} + + private V1Value(DeepgramListenProviderV1 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV1(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1Value && equalTo((V1Value) other); + } + + private boolean equalTo(V1Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentListenProvider{" + "value: " + value + "}"; + } + } + + @JsonTypeName("v2") + @JsonIgnoreProperties("version") + private static final class V2Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV2 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V2Value() {} + + private V2Value(DeepgramListenProviderV2 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV2(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V2Value && equalTo((V2Value) other); + } + + private boolean equalTo(V2Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentListenProvider{" + "value: " + value + "}"; + } + } + + @JsonIgnoreProperties("version") + private static final class _UnknownValue implements Value { + private String type; + + @JsonValue + private Object value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private _UnknownValue(@JsonProperty("value") Object value) {} + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor._visitUnknown(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof _UnknownValue && equalTo((_UnknownValue) other); + } + + private boolean equalTo(_UnknownValue other) { + return type.equals(other.type) && value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.type, this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentListenProvider{" + "type: " + type + ", value: " + value + "}"; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak new file mode 100644 index 0000000..443c214 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1SettingsApplied.Builder.class) +public final class AgentV1SettingsApplied { + private final Map additionalProperties; + + private AgentV1SettingsApplied(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for settings applied confirmation + */ + @JsonProperty("type") + public String getType() { + return "SettingsApplied"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1SettingsApplied; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1SettingsApplied other) { + return this; + } + + public AgentV1SettingsApplied build() { + return new AgentV1SettingsApplied(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak new file mode 100644 index 0000000..87c8d7d --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1SpeakUpdated.Builder.class) +public final class AgentV1SpeakUpdated { + private final Map additionalProperties; + + private AgentV1SpeakUpdated(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for speak update confirmation + */ + @JsonProperty("type") + public String getType() { + return "SpeakUpdated"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1SpeakUpdated; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1SpeakUpdated other) { + return this; + } + + public AgentV1SpeakUpdated build() { + return new AgentV1SpeakUpdated(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak new file mode 100644 index 0000000..4b6f623 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1ThinkUpdated.Builder.class) +public final class AgentV1ThinkUpdated { + private final Map additionalProperties; + + private AgentV1ThinkUpdated(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for think update confirmation + */ + @JsonProperty("type") + public String getType() { + return "ThinkUpdated"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1ThinkUpdated; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1ThinkUpdated other) { + return this; + } + + public AgentV1ThinkUpdated build() { + return new AgentV1ThinkUpdated(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak new file mode 100644 index 0000000..d0390e2 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak @@ -0,0 +1,226 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.types.DeepgramListenProviderV1; +import com.deepgram.types.DeepgramListenProviderV2; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Objects; +import java.util.Optional; + +public final class AgentV1UpdateListenListenProvider { + private final Value value; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + private AgentV1UpdateListenListenProvider(Value value) { + this.value = value; + } + + public T visit(Visitor visitor) { + return value.visit(visitor); + } + + public static AgentV1UpdateListenListenProvider v1(DeepgramListenProviderV1 value) { + return new AgentV1UpdateListenListenProvider(new V1Value(value)); + } + + public static AgentV1UpdateListenListenProvider v2(DeepgramListenProviderV2 value) { + return new AgentV1UpdateListenListenProvider(new V2Value(value)); + } + + public boolean isV1() { + return value instanceof V1Value; + } + + public boolean isV2() { + return value instanceof V2Value; + } + + public boolean _isUnknown() { + return value instanceof _UnknownValue; + } + + public Optional getV1() { + if (isV1()) { + return Optional.of(((V1Value) value).value); + } + return Optional.empty(); + } + + public Optional getV2() { + if (isV2()) { + return Optional.of(((V2Value) value).value); + } + return Optional.empty(); + } + + public Optional _getUnknown() { + if (_isUnknown()) { + return Optional.of(((_UnknownValue) value).value); + } + return Optional.empty(); + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1UpdateListenListenProvider + && value.equals(((AgentV1UpdateListenListenProvider) other).value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return value.toString(); + } + + @JsonValue + private Value getValue() { + return this.value; + } + + public interface Visitor { + T visitV1(DeepgramListenProviderV1 v1); + + T visitV2(DeepgramListenProviderV2 v2); + + T _visitUnknown(Object unknownType); + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) + @JsonIgnoreProperties(ignoreUnknown = true) + private interface Value { + T visit(Visitor visitor); + } + + @JsonTypeName("v1") + @JsonIgnoreProperties("version") + private static final class V1Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV1 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V1Value() {} + + private V1Value(DeepgramListenProviderV1 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV1(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1Value && equalTo((V1Value) other); + } + + private boolean equalTo(V1Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1UpdateListenListenProvider{" + "value: " + value + "}"; + } + } + + @JsonTypeName("v2") + @JsonIgnoreProperties("version") + private static final class V2Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV2 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V2Value() {} + + private V2Value(DeepgramListenProviderV2 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV2(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V2Value && equalTo((V2Value) other); + } + + private boolean equalTo(V2Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1UpdateListenListenProvider{" + "value: " + value + "}"; + } + } + + @JsonIgnoreProperties("version") + private static final class _UnknownValue implements Value { + private String type; + + @JsonValue + private Object value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private _UnknownValue(@JsonProperty("value") Object value) {} + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor._visitUnknown(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof _UnknownValue && equalTo((_UnknownValue) other); + } + + private boolean equalTo(_UnknownValue other) { + return type.equals(other.type) && value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.type, this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1UpdateListenListenProvider{" + "type: " + type + ", value: " + value + "}"; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak new file mode 100644 index 0000000..36e8a9d --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1UserStartedSpeaking.Builder.class) +public final class AgentV1UserStartedSpeaking { + private final Map additionalProperties; + + private AgentV1UserStartedSpeaking(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier indicating that the user has begun speaking + */ + @JsonProperty("type") + public String getType() { + return "UserStartedSpeaking"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1UserStartedSpeaking; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1UserStartedSpeaking other) { + return this; + } + + public AgentV1UserStartedSpeaking build() { + return new AgentV1UserStartedSpeaking(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak new file mode 100644 index 0000000..d8305b1 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak @@ -0,0 +1,868 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.agent.v1.websocket; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ClientOptions; +import com.deepgram.core.DisconnectReason; +import com.deepgram.core.ObjectMappers; +import com.deepgram.core.ReconnectingWebSocketListener; +import com.deepgram.core.RequestOptions; +import com.deepgram.core.WebSocketReadyState; +import com.deepgram.resources.agent.v1.types.AgentV1AgentAudioDone; +import com.deepgram.resources.agent.v1.types.AgentV1AgentStartedSpeaking; +import com.deepgram.resources.agent.v1.types.AgentV1AgentThinking; +import com.deepgram.resources.agent.v1.types.AgentV1ConversationText; +import com.deepgram.resources.agent.v1.types.AgentV1Error; +import com.deepgram.resources.agent.v1.types.AgentV1ForceEndTurn; +import com.deepgram.resources.agent.v1.types.AgentV1FunctionCallCancelled; +import com.deepgram.resources.agent.v1.types.AgentV1FunctionCallRequest; +import com.deepgram.resources.agent.v1.types.AgentV1History; +import com.deepgram.resources.agent.v1.types.AgentV1InjectAgentMessage; +import com.deepgram.resources.agent.v1.types.AgentV1InjectUserMessage; +import com.deepgram.resources.agent.v1.types.AgentV1InjectionRefused; +import com.deepgram.resources.agent.v1.types.AgentV1KeepAlive; +import com.deepgram.resources.agent.v1.types.AgentV1LatencyReport; +import com.deepgram.resources.agent.v1.types.AgentV1ListenUpdated; +import com.deepgram.resources.agent.v1.types.AgentV1PromptUpdated; +import com.deepgram.resources.agent.v1.types.AgentV1ReceiveFunctionCallResponse; +import com.deepgram.resources.agent.v1.types.AgentV1SendFunctionCallResponse; +import com.deepgram.resources.agent.v1.types.AgentV1Settings; +import com.deepgram.resources.agent.v1.types.AgentV1SettingsApplied; +import com.deepgram.resources.agent.v1.types.AgentV1SpeakUpdated; +import com.deepgram.resources.agent.v1.types.AgentV1ThinkUpdated; +import com.deepgram.resources.agent.v1.types.AgentV1UpdateListen; +import com.deepgram.resources.agent.v1.types.AgentV1UpdatePrompt; +import com.deepgram.resources.agent.v1.types.AgentV1UpdateSpeak; +import com.deepgram.resources.agent.v1.types.AgentV1UpdateThink; +import com.deepgram.resources.agent.v1.types.AgentV1UserStartedSpeaking; +import com.deepgram.resources.agent.v1.types.AgentV1Warning; +import com.deepgram.resources.agent.v1.types.AgentV1Welcome; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okio.ByteString; + +/** + * WebSocket client for the v1 channel. + * Provides real-time bidirectional communication with strongly-typed messages. + */ +public class V1WebSocketClient implements AutoCloseable { + protected final ClientOptions clientOptions; + + private final ObjectMapper objectMapper; + + private final OkHttpClient okHttpClient; + + private ScheduledExecutorService timeoutExecutor; + + private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; + + private volatile Runnable onConnectedHandler; + + private volatile Consumer onDisconnectedHandler; + + private volatile Consumer onErrorHandler; + + private volatile Consumer onMessageHandler; + + private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; + + private CompletableFuture connectionFuture; + + private ReconnectingWebSocketListener reconnectingListener; + + private volatile Consumer listenUpdatedHandler; + + private volatile Consumer thinkUpdatedHandler; + + private volatile Consumer onFunctionCallResponseHandler; + + private volatile Consumer promptUpdatedHandler; + + private volatile Consumer speakUpdatedHandler; + + private volatile Consumer injectionRefusedHandler; + + private volatile Consumer welcomeHandler; + + private volatile Consumer settingsAppliedHandler; + + private volatile Consumer conversationTextHandler; + + private volatile Consumer userStartedSpeakingHandler; + + private volatile Consumer agentThinkingHandler; + + private volatile Consumer latencyReportHandler; + + private volatile Consumer functionCallRequestHandler; + + private volatile Consumer functionCallCancelledHandler; + + private volatile Consumer agentStartedSpeakingHandler; + + private volatile Consumer agentAudioDoneHandler; + + private volatile Consumer errorHandler; + + private volatile Consumer warningHandler; + + private volatile Consumer agentV1HistoryHandler; + + private volatile Consumer agentV1AudioHandler; + + /** + * Creates a new async WebSocket client for the v1 channel. + */ + public V1WebSocketClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.objectMapper = ObjectMappers.JSON_MAPPER; + this.okHttpClient = clientOptions.httpClient(); + } + + /** + * Establishes the WebSocket connection asynchronously with automatic reconnection. + * @return a CompletableFuture that completes when the connection is established + */ + public CompletableFuture connect() { + connectionFuture = new CompletableFuture<>(); + String baseUrl = clientOptions.environment().getAgentURL(); + String fullPath = "/v1/agent/converse"; + if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { + fullPath = fullPath.substring(1); + } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { + fullPath = "/" + fullPath; + } + // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing + if (baseUrl.startsWith("wss://")) { + baseUrl = "https://" + baseUrl.substring(6); + } else if (baseUrl.startsWith("ws://")) { + baseUrl = "http://" + baseUrl.substring(5); + } + HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); + if (parsedUrl == null) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); + } + HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); + Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); + final Request request = requestBuilder.build(); + this.readyState = WebSocketReadyState.CONNECTING; + ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null + ? this.reconnectOptions + : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); + this.reconnectingListener = + new ReconnectingWebSocketListener(reconnectOpts, () -> { + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }) { + @Override + protected void onWebSocketOpen(WebSocket webSocket, Response response) { + readyState = WebSocketReadyState.OPEN; + if (onConnectedHandler != null) { + onConnectedHandler.run(); + } + connectionFuture.complete(null); + } + + @Override + protected void onWebSocketMessage(WebSocket webSocket, String text) { + handleIncomingMessage(text); + } + + @Override + protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { + if (agentV1AudioHandler != null) { + agentV1AudioHandler.accept(bytes); + } + } + + @Override + protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { + readyState = WebSocketReadyState.CLOSED; + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException(t)); + } + connectionFuture.completeExceptionally(t); + } + + @Override + protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { + readyState = WebSocketReadyState.CLOSED; + if (onDisconnectedHandler != null) { + onDisconnectedHandler.accept(new DisconnectReason(code, reason)); + } + } + }; + reconnectingListener.connect(); + return connectionFuture; + } + + /** + * Disconnects the WebSocket connection and releases resources. + */ + public void disconnect() { + if (reconnectingListener != null) { + reconnectingListener.disconnect(); + } + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + } + + /** + * Gets the current state of the WebSocket connection. + * + * This provides the actual connection state, similar to the W3C WebSocket API. + * + * @return the current WebSocket ready state + */ + public WebSocketReadyState getReadyState() { + return readyState; + } + + /** + * Sends an AgentV1Settings message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendSettings(AgentV1Settings message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1UpdateListen message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendUpdateListen(AgentV1UpdateListen message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1UpdateThink message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendUpdateThink(AgentV1UpdateThink message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1UpdateSpeak message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendUpdateSpeak(AgentV1UpdateSpeak message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1InjectUserMessage message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendInjectUserMessage(AgentV1InjectUserMessage message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1InjectAgentMessage message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendInjectAgentMessage(AgentV1InjectAgentMessage message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1SendFunctionCallResponse message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendFunctionCallResponse(AgentV1SendFunctionCallResponse message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1KeepAlive message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendKeepAlive(AgentV1KeepAlive message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1UpdatePrompt message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendUpdatePrompt(AgentV1UpdatePrompt message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1ForceEndTurn message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendForceEndTurn(AgentV1ForceEndTurn message) { + return sendMessage(message); + } + + /** + * Sends an AgentV1Media message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendMedia(ByteString message) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + // Use reconnecting listener's sendBinary method which handles queuing + reconnectingListener.sendBinary(message); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); + } + return future; + } + + /** + * Registers a handler for AgentV1ListenUpdated messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onListenUpdated(Consumer handler) { + this.listenUpdatedHandler = handler; + } + + /** + * Registers a handler for AgentV1ThinkUpdated messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onThinkUpdated(Consumer handler) { + this.thinkUpdatedHandler = handler; + } + + /** + * Registers a handler for AgentV1ReceiveFunctionCallResponse messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onFunctionCallResponse(Consumer handler) { + this.onFunctionCallResponseHandler = handler; + } + + /** + * Registers a handler for AgentV1PromptUpdated messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onPromptUpdated(Consumer handler) { + this.promptUpdatedHandler = handler; + } + + /** + * Registers a handler for AgentV1SpeakUpdated messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeakUpdated(Consumer handler) { + this.speakUpdatedHandler = handler; + } + + /** + * Registers a handler for AgentV1InjectionRefused messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onInjectionRefused(Consumer handler) { + this.injectionRefusedHandler = handler; + } + + /** + * Registers a handler for AgentV1Welcome messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onWelcome(Consumer handler) { + this.welcomeHandler = handler; + } + + /** + * Registers a handler for AgentV1SettingsApplied messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSettingsApplied(Consumer handler) { + this.settingsAppliedHandler = handler; + } + + /** + * Registers a handler for AgentV1ConversationText messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConversationText(Consumer handler) { + this.conversationTextHandler = handler; + } + + /** + * Registers a handler for AgentV1UserStartedSpeaking messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onUserStartedSpeaking(Consumer handler) { + this.userStartedSpeakingHandler = handler; + } + + /** + * Registers a handler for AgentV1AgentThinking messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onAgentThinking(Consumer handler) { + this.agentThinkingHandler = handler; + } + + /** + * Registers a handler for AgentV1LatencyReport messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onLatencyReport(Consumer handler) { + this.latencyReportHandler = handler; + } + + /** + * Registers a handler for AgentV1FunctionCallRequest messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onFunctionCallRequest(Consumer handler) { + this.functionCallRequestHandler = handler; + } + + /** + * Registers a handler for AgentV1FunctionCallCancelled messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onFunctionCallCancelled(Consumer handler) { + this.functionCallCancelledHandler = handler; + } + + /** + * Registers a handler for AgentV1AgentStartedSpeaking messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onAgentStartedSpeaking(Consumer handler) { + this.agentStartedSpeakingHandler = handler; + } + + /** + * Registers a handler for AgentV1AgentAudioDone messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onAgentAudioDone(Consumer handler) { + this.agentAudioDoneHandler = handler; + } + + /** + * Registers a handler for AgentV1Error messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onErrorMessage(Consumer handler) { + this.errorHandler = handler; + } + + /** + * Registers a handler for AgentV1Warning messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onWarning(Consumer handler) { + this.warningHandler = handler; + } + + /** + * Registers a handler for AgentV1History messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onAgentV1History(Consumer handler) { + this.agentV1HistoryHandler = handler; + } + + /** + * Registers a handler for AgentV1Audio messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onAgentV1Audio(Consumer handler) { + this.agentV1AudioHandler = handler; + } + + /** + * Registers a handler called when the connection is established. + * @param handler the handler to invoke when connected + */ + public void onConnected(Runnable handler) { + this.onConnectedHandler = handler; + } + + /** + * Registers a handler called when the connection is closed. + * @param handler the handler to invoke when disconnected + */ + public void onDisconnected(Consumer handler) { + this.onDisconnectedHandler = handler; + } + + /** + * Registers a handler called when an error occurs. + * @param handler the handler to invoke on error + */ + public void onError(Consumer handler) { + this.onErrorHandler = handler; + } + + /** + * Registers a handler called for every incoming text message. + * The handler receives the raw JSON string before type-specific dispatch. + * @param handler the handler to invoke with the raw message JSON + */ + public void onMessage(Consumer handler) { + this.onMessageHandler = handler; + } + + /** + * Configures reconnection behavior. Must be called before {@link #connect}. + * + * @param options the reconnection options (backoff, retries, queue size) + */ + public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { + this.reconnectOptions = options; + } + + /** + * Closes this WebSocket client, releasing all resources. + * Equivalent to calling {@link #disconnect()}. + */ + @Override + public void close() { + disconnect(); + } + + /** + * Ensures the WebSocket is connected and ready to send messages. + * @throws IllegalStateException if the socket is not connected or not open + */ + private void assertSocketIsOpen() { + if (reconnectingListener.getWebSocket() == null) { + throw new IllegalStateException("WebSocket is not connected. Call connect() first."); + } + if (readyState != WebSocketReadyState.OPEN) { + throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); + } + } + + private CompletableFuture sendMessage(Object body) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + String json = objectMapper.writeValueAsString(body); + // Use reconnecting listener's send method which handles queuing + reconnectingListener.send(json); + future.complete(null); + } catch (IllegalStateException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send message", e)); + } + return future; + } + + private void handleIncomingMessage(String json) { + try { + if (onMessageHandler != null) { + onMessageHandler.accept(json); + } + JsonNode node = objectMapper.readTree(json); + if (node == null || node.isNull()) { + throw new IllegalArgumentException("Received null or invalid JSON message"); + } + if (node.has("total_latency") + && node.has("tts_latency") + && node.has("ttt_latency") + && "AgentStartedSpeaking".equals(node.path("type").asText())) { + AgentV1AgentStartedSpeaking agentStartedSpeakingHandlerEvent = null; + try { + agentStartedSpeakingHandlerEvent = + objectMapper.treeToValue(node, AgentV1AgentStartedSpeaking.class); + } catch (Exception e) { + } + if (agentStartedSpeakingHandlerEvent != null) { + if (agentStartedSpeakingHandler != null) { + agentStartedSpeakingHandler.accept(agentStartedSpeakingHandlerEvent); + } + return; + } + } + if (node.has("name") + && node.has("content") + && "FunctionCallResponse".equals(node.path("type").asText())) { + AgentV1ReceiveFunctionCallResponse onFunctionCallResponseHandlerEvent = null; + try { + onFunctionCallResponseHandlerEvent = + objectMapper.treeToValue(node, AgentV1ReceiveFunctionCallResponse.class); + } catch (Exception e) { + } + if (onFunctionCallResponseHandlerEvent != null) { + if (onFunctionCallResponseHandler != null) { + onFunctionCallResponseHandler.accept(onFunctionCallResponseHandlerEvent); + } + return; + } + } + if (node.has("role") + && node.has("content") + && "ConversationText".equals(node.path("type").asText())) { + AgentV1ConversationText conversationTextHandlerEvent = null; + try { + conversationTextHandlerEvent = objectMapper.treeToValue(node, AgentV1ConversationText.class); + } catch (Exception e) { + } + if (conversationTextHandlerEvent != null) { + if (conversationTextHandler != null) { + conversationTextHandler.accept(conversationTextHandlerEvent); + } + return; + } + } + if (node.has("description") + && node.has("code") + && "Error".equals(node.path("type").asText())) { + AgentV1Error errorHandlerEvent = null; + try { + errorHandlerEvent = objectMapper.treeToValue(node, AgentV1Error.class); + } catch (Exception e) { + } + if (errorHandlerEvent != null) { + if (errorHandler != null) { + errorHandler.accept(errorHandlerEvent); + } + return; + } + } + if (node.has("description") + && node.has("code") + && "Warning".equals(node.path("type").asText())) { + AgentV1Warning warningHandlerEvent = null; + try { + warningHandlerEvent = objectMapper.treeToValue(node, AgentV1Warning.class); + } catch (Exception e) { + } + if (warningHandlerEvent != null) { + if (warningHandler != null) { + warningHandler.accept(warningHandlerEvent); + } + return; + } + } + if (node.has("message") + && "InjectionRefused".equals(node.path("type").asText())) { + AgentV1InjectionRefused injectionRefusedHandlerEvent = null; + try { + injectionRefusedHandlerEvent = objectMapper.treeToValue(node, AgentV1InjectionRefused.class); + } catch (Exception e) { + } + if (injectionRefusedHandlerEvent != null) { + if (injectionRefusedHandler != null) { + injectionRefusedHandler.accept(injectionRefusedHandlerEvent); + } + return; + } + } + if (node.has("request_id") && "Welcome".equals(node.path("type").asText())) { + AgentV1Welcome welcomeHandlerEvent = null; + try { + welcomeHandlerEvent = objectMapper.treeToValue(node, AgentV1Welcome.class); + } catch (Exception e) { + } + if (welcomeHandlerEvent != null) { + if (welcomeHandler != null) { + welcomeHandler.accept(welcomeHandlerEvent); + } + return; + } + } + if (node.has("content") && "AgentThinking".equals(node.path("type").asText())) { + AgentV1AgentThinking agentThinkingHandlerEvent = null; + try { + agentThinkingHandlerEvent = objectMapper.treeToValue(node, AgentV1AgentThinking.class); + } catch (Exception e) { + } + if (agentThinkingHandlerEvent != null) { + if (agentThinkingHandler != null) { + agentThinkingHandler.accept(agentThinkingHandlerEvent); + } + return; + } + } + if (node.has("functions") + && "FunctionCallRequest".equals(node.path("type").asText())) { + AgentV1FunctionCallRequest functionCallRequestHandlerEvent = null; + try { + functionCallRequestHandlerEvent = objectMapper.treeToValue(node, AgentV1FunctionCallRequest.class); + } catch (Exception e) { + } + if (functionCallRequestHandlerEvent != null) { + if (functionCallRequestHandler != null) { + functionCallRequestHandler.accept(functionCallRequestHandlerEvent); + } + return; + } + } + if (node.has("functions") + && "FunctionCallCancelled".equals(node.path("type").asText())) { + AgentV1FunctionCallCancelled functionCallCancelledHandlerEvent = null; + try { + functionCallCancelledHandlerEvent = + objectMapper.treeToValue(node, AgentV1FunctionCallCancelled.class); + } catch (Exception e) { + } + if (functionCallCancelledHandlerEvent != null) { + if (functionCallCancelledHandler != null) { + functionCallCancelledHandler.accept(functionCallCancelledHandlerEvent); + } + return; + } + } + if ("ListenUpdated".equals(node.path("type").asText())) { + AgentV1ListenUpdated listenUpdatedHandlerEvent = null; + try { + listenUpdatedHandlerEvent = objectMapper.treeToValue(node, AgentV1ListenUpdated.class); + } catch (Exception e) { + } + if (listenUpdatedHandlerEvent != null) { + if (listenUpdatedHandler != null) { + listenUpdatedHandler.accept(listenUpdatedHandlerEvent); + } + return; + } + } + if ("ThinkUpdated".equals(node.path("type").asText())) { + AgentV1ThinkUpdated thinkUpdatedHandlerEvent = null; + try { + thinkUpdatedHandlerEvent = objectMapper.treeToValue(node, AgentV1ThinkUpdated.class); + } catch (Exception e) { + } + if (thinkUpdatedHandlerEvent != null) { + if (thinkUpdatedHandler != null) { + thinkUpdatedHandler.accept(thinkUpdatedHandlerEvent); + } + return; + } + } + if ("PromptUpdated".equals(node.path("type").asText())) { + AgentV1PromptUpdated promptUpdatedHandlerEvent = null; + try { + promptUpdatedHandlerEvent = objectMapper.treeToValue(node, AgentV1PromptUpdated.class); + } catch (Exception e) { + } + if (promptUpdatedHandlerEvent != null) { + if (promptUpdatedHandler != null) { + promptUpdatedHandler.accept(promptUpdatedHandlerEvent); + } + return; + } + } + if ("SpeakUpdated".equals(node.path("type").asText())) { + AgentV1SpeakUpdated speakUpdatedHandlerEvent = null; + try { + speakUpdatedHandlerEvent = objectMapper.treeToValue(node, AgentV1SpeakUpdated.class); + } catch (Exception e) { + } + if (speakUpdatedHandlerEvent != null) { + if (speakUpdatedHandler != null) { + speakUpdatedHandler.accept(speakUpdatedHandlerEvent); + } + return; + } + } + if ("SettingsApplied".equals(node.path("type").asText())) { + AgentV1SettingsApplied settingsAppliedHandlerEvent = null; + try { + settingsAppliedHandlerEvent = objectMapper.treeToValue(node, AgentV1SettingsApplied.class); + } catch (Exception e) { + } + if (settingsAppliedHandlerEvent != null) { + if (settingsAppliedHandler != null) { + settingsAppliedHandler.accept(settingsAppliedHandlerEvent); + } + return; + } + } + if ("UserStartedSpeaking".equals(node.path("type").asText())) { + AgentV1UserStartedSpeaking userStartedSpeakingHandlerEvent = null; + try { + userStartedSpeakingHandlerEvent = objectMapper.treeToValue(node, AgentV1UserStartedSpeaking.class); + } catch (Exception e) { + } + if (userStartedSpeakingHandlerEvent != null) { + if (userStartedSpeakingHandler != null) { + userStartedSpeakingHandler.accept(userStartedSpeakingHandlerEvent); + } + return; + } + } + if ("LatencyReport".equals(node.path("type").asText())) { + AgentV1LatencyReport latencyReportHandlerEvent = null; + try { + latencyReportHandlerEvent = objectMapper.treeToValue(node, AgentV1LatencyReport.class); + } catch (Exception e) { + } + if (latencyReportHandlerEvent != null) { + if (latencyReportHandler != null) { + latencyReportHandler.accept(latencyReportHandlerEvent); + } + return; + } + } + if ("AgentAudioDone".equals(node.path("type").asText())) { + AgentV1AgentAudioDone agentAudioDoneHandlerEvent = null; + try { + agentAudioDoneHandlerEvent = objectMapper.treeToValue(node, AgentV1AgentAudioDone.class); + } catch (Exception e) { + } + if (agentAudioDoneHandlerEvent != null) { + if (agentAudioDoneHandler != null) { + agentAudioDoneHandler.accept(agentAudioDoneHandlerEvent); + } + return; + } + } + AgentV1History agentV1HistoryHandlerEvent = null; + try { + agentV1HistoryHandlerEvent = objectMapper.treeToValue(node, AgentV1History.class); + } catch (Exception e) { + } + if (agentV1HistoryHandlerEvent != null) { + if (agentV1HistoryHandler != null) { + agentV1HistoryHandler.accept(agentV1HistoryHandlerEvent); + } + return; + } + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException( + "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) + + "... Update your SDK version to support new message types.")); + } + } catch (Exception e) { + if (onErrorHandler != null) { + onErrorHandler.accept(e); + } + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak new file mode 100644 index 0000000..ffaa903 --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak @@ -0,0 +1,545 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v1.websocket; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ClientOptions; +import com.deepgram.core.DisconnectReason; +import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; +import com.deepgram.core.ReconnectingWebSocketListener; +import com.deepgram.core.RequestOptions; +import com.deepgram.core.WebSocketReadyState; +import com.deepgram.resources.listen.v1.types.ListenV1CloseStream; +import com.deepgram.resources.listen.v1.types.ListenV1Finalize; +import com.deepgram.resources.listen.v1.types.ListenV1KeepAlive; +import com.deepgram.resources.listen.v1.types.ListenV1Metadata; +import com.deepgram.resources.listen.v1.types.ListenV1Results; +import com.deepgram.resources.listen.v1.types.ListenV1SpeechStarted; +import com.deepgram.resources.listen.v1.types.ListenV1UtteranceEnd; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okio.ByteString; + +/** + * WebSocket client for the v1 channel. + * Provides real-time bidirectional communication with strongly-typed messages. + */ +public class V1WebSocketClient implements AutoCloseable { + protected final ClientOptions clientOptions; + + private final ObjectMapper objectMapper; + + private final OkHttpClient okHttpClient; + + private ScheduledExecutorService timeoutExecutor; + + private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; + + private volatile Runnable onConnectedHandler; + + private volatile Consumer onDisconnectedHandler; + + private volatile Consumer onErrorHandler; + + private volatile Consumer onMessageHandler; + + private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; + + private CompletableFuture connectionFuture; + + private ReconnectingWebSocketListener reconnectingListener; + + private volatile Consumer resultsHandler; + + private volatile Consumer metadataHandler; + + private volatile Consumer utteranceEndHandler; + + private volatile Consumer speechStartedHandler; + + /** + * Creates a new async WebSocket client for the v1 channel. + */ + public V1WebSocketClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.objectMapper = ObjectMappers.JSON_MAPPER; + this.okHttpClient = clientOptions.httpClient(); + } + + /** + * Establishes the WebSocket connection asynchronously with automatic reconnection. + * @return a CompletableFuture that completes when the connection is established + * @param options connection options including query parameters + */ + public CompletableFuture connect(V1ConnectOptions options) { + connectionFuture = new CompletableFuture<>(); + String baseUrl = clientOptions.environment().getProductionURL(); + String fullPath = "/v1/listen"; + if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { + fullPath = fullPath.substring(1); + } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { + fullPath = "/" + fullPath; + } + // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing + if (baseUrl.startsWith("wss://")) { + baseUrl = "https://" + baseUrl.substring(6); + } else if (baseUrl.startsWith("ws://")) { + baseUrl = "http://" + baseUrl.substring(5); + } + HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); + if (parsedUrl == null) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); + } + HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); + if (options.getCallback() != null && options.getCallback().isPresent()) { + urlBuilder.addQueryParameter( + "callback", String.valueOf(options.getCallback().get())); + } + if (options.getCallbackMethod() != null && options.getCallbackMethod().isPresent()) { + urlBuilder.addQueryParameter( + "callback_method", + String.valueOf(options.getCallbackMethod().get())); + } + if (options.getChannels() != null && options.getChannels().isPresent()) { + urlBuilder.addQueryParameter( + "channels", String.valueOf(options.getChannels().get())); + } + if (options.getDetectEntities() != null && options.getDetectEntities().isPresent()) { + urlBuilder.addQueryParameter( + "detect_entities", + String.valueOf(options.getDetectEntities().get())); + } + if (options.getDiarize() != null && options.getDiarize().isPresent()) { + urlBuilder.addQueryParameter( + "diarize", String.valueOf(options.getDiarize().get())); + } + if (options.getDiarizeModel() != null && options.getDiarizeModel().isPresent()) { + urlBuilder.addQueryParameter( + "diarize_model", String.valueOf(options.getDiarizeModel().get())); + } + if (options.getDictation() != null && options.getDictation().isPresent()) { + urlBuilder.addQueryParameter( + "dictation", String.valueOf(options.getDictation().get())); + } + if (options.getEncoding() != null && options.getEncoding().isPresent()) { + urlBuilder.addQueryParameter( + "encoding", String.valueOf(options.getEncoding().get())); + } + if (options.getEndpointing() != null && options.getEndpointing().isPresent()) { + urlBuilder.addQueryParameter( + "endpointing", String.valueOf(options.getEndpointing().get())); + } + if (options.getExtra() != null && options.getExtra().isPresent()) { + QueryStringMapper.addQueryParameter(urlBuilder, "extra", options.getExtra().get().get(), true); + } + if (options.getInterimResults() != null && options.getInterimResults().isPresent()) { + urlBuilder.addQueryParameter( + "interim_results", + String.valueOf(options.getInterimResults().get())); + } + if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { + QueryStringMapper.addQueryParameter(urlBuilder, "keyterm", options.getKeyterm().get().get(), true); + } + if (options.getKeywords() != null && options.getKeywords().isPresent()) { + QueryStringMapper.addQueryParameter(urlBuilder, "keywords", options.getKeywords().get().get(), true); + } + if (options.getLanguage() != null && options.getLanguage().isPresent()) { + urlBuilder.addQueryParameter( + "language", String.valueOf(options.getLanguage().get())); + } + if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { + urlBuilder.addQueryParameter( + "mip_opt_out", String.valueOf(options.getMipOptOut().get())); + } + urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); + if (options.getMultichannel() != null && options.getMultichannel().isPresent()) { + urlBuilder.addQueryParameter( + "multichannel", String.valueOf(options.getMultichannel().get())); + } + if (options.getNumerals() != null && options.getNumerals().isPresent()) { + urlBuilder.addQueryParameter( + "numerals", String.valueOf(options.getNumerals().get())); + } + if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { + urlBuilder.addQueryParameter( + "profanity_filter", + String.valueOf(options.getProfanityFilter().get())); + } + if (options.getPunctuate() != null && options.getPunctuate().isPresent()) { + urlBuilder.addQueryParameter( + "punctuate", String.valueOf(options.getPunctuate().get())); + } + if (options.getRedact() != null && options.getRedact().isPresent()) { + urlBuilder.addQueryParameter( + "redact", String.valueOf(options.getRedact().get())); + } + if (options.getReplace() != null && options.getReplace().isPresent()) { + QueryStringMapper.addQueryParameter(urlBuilder, "replace", options.getReplace().get().get(), true); + } + if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { + urlBuilder.addQueryParameter( + "sample_rate", String.valueOf(options.getSampleRate().get())); + } + if (options.getSearch() != null && options.getSearch().isPresent()) { + QueryStringMapper.addQueryParameter(urlBuilder, "search", options.getSearch().get().get(), true); + } + if (options.getSmartFormat() != null && options.getSmartFormat().isPresent()) { + urlBuilder.addQueryParameter( + "smart_format", String.valueOf(options.getSmartFormat().get())); + } + if (options.getTag() != null && options.getTag().isPresent()) { + QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); + } + if (options.getUtteranceEndMs() != null && options.getUtteranceEndMs().isPresent()) { + urlBuilder.addQueryParameter( + "utterance_end_ms", + String.valueOf(options.getUtteranceEndMs().get())); + } + if (options.getVadEvents() != null && options.getVadEvents().isPresent()) { + urlBuilder.addQueryParameter( + "vad_events", String.valueOf(options.getVadEvents().get())); + } + if (options.getVersion() != null && options.getVersion().isPresent()) { + urlBuilder.addQueryParameter( + "version", String.valueOf(options.getVersion().get())); + } + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } + Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); + final Request request = requestBuilder.build(); + this.readyState = WebSocketReadyState.CONNECTING; + ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null + ? this.reconnectOptions + : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); + this.reconnectingListener = + new ReconnectingWebSocketListener(reconnectOpts, () -> { + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }) { + @Override + protected void onWebSocketOpen(WebSocket webSocket, Response response) { + readyState = WebSocketReadyState.OPEN; + if (onConnectedHandler != null) { + onConnectedHandler.run(); + } + connectionFuture.complete(null); + } + + @Override + protected void onWebSocketMessage(WebSocket webSocket, String text) { + handleIncomingMessage(text); + } + + @Override + protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) {} + + @Override + protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { + readyState = WebSocketReadyState.CLOSED; + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException(t)); + } + connectionFuture.completeExceptionally(t); + } + + @Override + protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { + readyState = WebSocketReadyState.CLOSED; + if (onDisconnectedHandler != null) { + onDisconnectedHandler.accept(new DisconnectReason(code, reason)); + } + } + }; + reconnectingListener.connect(); + return connectionFuture; + } + + /** + * Disconnects the WebSocket connection and releases resources. + */ + public void disconnect() { + if (reconnectingListener != null) { + reconnectingListener.disconnect(); + } + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + } + + /** + * Gets the current state of the WebSocket connection. + * + * This provides the actual connection state, similar to the W3C WebSocket API. + * + * @return the current WebSocket ready state + */ + public WebSocketReadyState getReadyState() { + return readyState; + } + + /** + * Sends a ListenV1Media message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendMedia(ByteString message) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + // Use reconnecting listener's sendBinary method which handles queuing + reconnectingListener.sendBinary(message); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); + } + return future; + } + + /** + * Sends a ListenV1Finalize message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendFinalize(ListenV1Finalize message) { + return sendMessage(message); + } + + /** + * Sends a ListenV1CloseStream message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendCloseStream(ListenV1CloseStream message) { + return sendMessage(message); + } + + /** + * Sends a ListenV1KeepAlive message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendKeepAlive(ListenV1KeepAlive message) { + return sendMessage(message); + } + + /** + * Registers a handler for ListenV1Results messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onResults(Consumer handler) { + this.resultsHandler = handler; + } + + /** + * Registers a handler for ListenV1Metadata messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onMetadata(Consumer handler) { + this.metadataHandler = handler; + } + + /** + * Registers a handler for ListenV1UtteranceEnd messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onUtteranceEnd(Consumer handler) { + this.utteranceEndHandler = handler; + } + + /** + * Registers a handler for ListenV1SpeechStarted messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeechStarted(Consumer handler) { + this.speechStartedHandler = handler; + } + + /** + * Registers a handler called when the connection is established. + * @param handler the handler to invoke when connected + */ + public void onConnected(Runnable handler) { + this.onConnectedHandler = handler; + } + + /** + * Registers a handler called when the connection is closed. + * @param handler the handler to invoke when disconnected + */ + public void onDisconnected(Consumer handler) { + this.onDisconnectedHandler = handler; + } + + /** + * Registers a handler called when an error occurs. + * @param handler the handler to invoke on error + */ + public void onError(Consumer handler) { + this.onErrorHandler = handler; + } + + /** + * Registers a handler called for every incoming text message. + * The handler receives the raw JSON string before type-specific dispatch. + * @param handler the handler to invoke with the raw message JSON + */ + public void onMessage(Consumer handler) { + this.onMessageHandler = handler; + } + + /** + * Configures reconnection behavior. Must be called before {@link #connect}. + * + * @param options the reconnection options (backoff, retries, queue size) + */ + public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { + this.reconnectOptions = options; + } + + /** + * Closes this WebSocket client, releasing all resources. + * Equivalent to calling {@link #disconnect()}. + */ + @Override + public void close() { + disconnect(); + } + + /** + * Ensures the WebSocket is connected and ready to send messages. + * @throws IllegalStateException if the socket is not connected or not open + */ + private void assertSocketIsOpen() { + if (reconnectingListener.getWebSocket() == null) { + throw new IllegalStateException("WebSocket is not connected. Call connect() first."); + } + if (readyState != WebSocketReadyState.OPEN) { + throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); + } + } + + private CompletableFuture sendMessage(Object body) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + String json = objectMapper.writeValueAsString(body); + // Use reconnecting listener's send method which handles queuing + reconnectingListener.send(json); + future.complete(null); + } catch (IllegalStateException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send message", e)); + } + return future; + } + + private void handleIncomingMessage(String json) { + try { + if (onMessageHandler != null) { + onMessageHandler.accept(json); + } + JsonNode node = objectMapper.readTree(json); + if (node == null || node.isNull()) { + throw new IllegalArgumentException("Received null or invalid JSON message"); + } + if (node.has("transaction_key") + && node.has("request_id") + && node.has("sha256") + && node.has("created") + && node.has("duration") + && node.has("channels") + && "Metadata".equals(node.path("type").asText())) { + ListenV1Metadata metadataHandlerEvent = null; + try { + metadataHandlerEvent = objectMapper.treeToValue(node, ListenV1Metadata.class); + } catch (Exception e) { + } + if (metadataHandlerEvent != null) { + if (metadataHandler != null) { + metadataHandler.accept(metadataHandlerEvent); + } + return; + } + } + if (node.has("channel_index") + && node.has("duration") + && node.has("start") + && node.has("channel") + && node.has("metadata") + && "Results".equals(node.path("type").asText())) { + ListenV1Results resultsHandlerEvent = null; + try { + resultsHandlerEvent = objectMapper.treeToValue(node, ListenV1Results.class); + } catch (Exception e) { + } + if (resultsHandlerEvent != null) { + if (resultsHandler != null) { + resultsHandler.accept(resultsHandlerEvent); + } + return; + } + } + if (node.has("channel") + && node.has("last_word_end") + && "UtteranceEnd".equals(node.path("type").asText())) { + ListenV1UtteranceEnd utteranceEndHandlerEvent = null; + try { + utteranceEndHandlerEvent = objectMapper.treeToValue(node, ListenV1UtteranceEnd.class); + } catch (Exception e) { + } + if (utteranceEndHandlerEvent != null) { + if (utteranceEndHandler != null) { + utteranceEndHandler.accept(utteranceEndHandlerEvent); + } + return; + } + } + if (node.has("channel") + && node.has("timestamp") + && "SpeechStarted".equals(node.path("type").asText())) { + ListenV1SpeechStarted speechStartedHandlerEvent = null; + try { + speechStartedHandlerEvent = objectMapper.treeToValue(node, ListenV1SpeechStarted.class); + } catch (Exception e) { + } + if (speechStartedHandlerEvent != null) { + if (speechStartedHandler != null) { + speechStartedHandler.accept(speechStartedHandlerEvent); + } + return; + } + } + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException( + "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) + + "... Update your SDK version to support new message types.")); + } + } catch (Exception e) { + if (onErrorHandler != null) { + onErrorHandler.accept(e); + } + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak new file mode 100644 index 0000000..8f7496d --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v2.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ListenV2CloseStream.Builder.class) +public final class ListenV2CloseStream { + private final Map additionalProperties; + + private ListenV2CloseStream(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "CloseStream"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ListenV2CloseStream; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ListenV2CloseStream other) { + return this; + } + + public ListenV2CloseStream build() { + return new ListenV2CloseStream(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak new file mode 100644 index 0000000..99471a5 --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v2.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ListenV2ForceEndTurn.Builder.class) +public final class ListenV2ForceEndTurn { + private final Map additionalProperties; + + private ListenV2ForceEndTurn(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "ForceEndTurn"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ListenV2ForceEndTurn; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ListenV2ForceEndTurn other) { + return this; + } + + public ListenV2ForceEndTurn build() { + return new ListenV2ForceEndTurn(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak new file mode 100644 index 0000000..b2dee9b --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak @@ -0,0 +1,532 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v2.websocket; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ClientOptions; +import com.deepgram.core.DisconnectReason; +import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; +import com.deepgram.core.ReconnectingWebSocketListener; +import com.deepgram.core.RequestOptions; +import com.deepgram.core.WebSocketReadyState; +import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; +import com.deepgram.resources.listen.v2.types.ListenV2Configure; +import com.deepgram.resources.listen.v2.types.ListenV2ConfigureFailure; +import com.deepgram.resources.listen.v2.types.ListenV2ConfigureSuccess; +import com.deepgram.resources.listen.v2.types.ListenV2Connected; +import com.deepgram.resources.listen.v2.types.ListenV2FatalError; +import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; +import com.deepgram.resources.listen.v2.types.ListenV2TurnInfo; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okio.ByteString; + +/** + * WebSocket client for the v2 channel. + * Provides real-time bidirectional communication with strongly-typed messages. + */ +public class V2WebSocketClient implements AutoCloseable { + protected final ClientOptions clientOptions; + + private final ObjectMapper objectMapper; + + private final OkHttpClient okHttpClient; + + private ScheduledExecutorService timeoutExecutor; + + private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; + + private volatile Runnable onConnectedHandler; + + private volatile Consumer onDisconnectedHandler; + + private volatile Consumer onErrorHandler; + + private volatile Consumer onMessageHandler; + + private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; + + private CompletableFuture connectionFuture; + + private ReconnectingWebSocketListener reconnectingListener; + + private final AtomicReference closeStreamSocket = new AtomicReference<>(); + + private volatile Consumer connectedHandler; + + private volatile Consumer turnInfoHandler; + + private volatile Consumer configureSuccessHandler; + + private volatile Consumer configureFailureHandler; + + private volatile Consumer errorHandler; + + /** + * Creates a new async WebSocket client for the v2 channel. + */ + public V2WebSocketClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.objectMapper = ObjectMappers.JSON_MAPPER; + this.okHttpClient = clientOptions.httpClient(); + } + + /** + * Establishes the WebSocket connection asynchronously with automatic reconnection. + * @return a CompletableFuture that completes when the connection is established + * @param options connection options including query parameters + */ + public CompletableFuture connect(V2ConnectOptions options) { + connectionFuture = new CompletableFuture<>(); + closeStreamSocket.set(null); + String baseUrl = clientOptions.environment().getProductionURL(); + String fullPath = "/v2/listen"; + if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { + fullPath = fullPath.substring(1); + } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { + fullPath = "/" + fullPath; + } + // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing + if (baseUrl.startsWith("wss://")) { + baseUrl = "https://" + baseUrl.substring(6); + } else if (baseUrl.startsWith("ws://")) { + baseUrl = "http://" + baseUrl.substring(5); + } + HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); + if (parsedUrl == null) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); + } + HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); + urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); + if (options.getEncoding() != null && options.getEncoding().isPresent()) { + urlBuilder.addQueryParameter( + "encoding", String.valueOf(options.getEncoding().get())); + } + if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { + urlBuilder.addQueryParameter( + "sample_rate", String.valueOf(options.getSampleRate().get())); + } + if (options.getEagerEotThreshold() != null + && options.getEagerEotThreshold().isPresent()) { + urlBuilder.addQueryParameter( + "eager_eot_threshold", + String.valueOf(options.getEagerEotThreshold().get())); + } + if (options.getEotThreshold() != null && options.getEotThreshold().isPresent()) { + urlBuilder.addQueryParameter( + "eot_threshold", String.valueOf(options.getEotThreshold().get())); + } + if (options.getEotTimeoutMs() != null && options.getEotTimeoutMs().isPresent()) { + urlBuilder.addQueryParameter( + "eot_timeout_ms", String.valueOf(options.getEotTimeoutMs().get())); + } + if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { + QueryStringMapper.addQueryParameter( + urlBuilder, "keyterm", options.getKeyterm().get().get(), true); + } + if (options.getLanguageHint() != null && options.getLanguageHint().isPresent()) { + QueryStringMapper.addQueryParameter( + urlBuilder, "language_hint", options.getLanguageHint().get().get(), true); + } + if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { + urlBuilder.addQueryParameter( + "profanity_filter", + String.valueOf(options.getProfanityFilter().get())); + } + if (options.getNumerals() != null && options.getNumerals().isPresent()) { + urlBuilder.addQueryParameter( + "numerals", String.valueOf(options.getNumerals().get())); + } + if (options.getRedact() != null && options.getRedact().isPresent()) { + urlBuilder.addQueryParameter( + "redact", String.valueOf(options.getRedact().get())); + } + if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { + urlBuilder.addQueryParameter( + "mip_opt_out", String.valueOf(options.getMipOptOut().get())); + } + if (options.getTag() != null && options.getTag().isPresent()) { + QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); + } + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } + Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); + final Request request = requestBuilder.build(); + this.readyState = WebSocketReadyState.CONNECTING; + ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null + ? this.reconnectOptions + : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); + this.reconnectingListener = + new ReconnectingWebSocketListener(reconnectOpts, () -> { + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }) { + @Override + protected void onWebSocketOpen(WebSocket webSocket, Response response) { + closeStreamSocket.set(null); + readyState = WebSocketReadyState.OPEN; + if (onConnectedHandler != null) { + onConnectedHandler.run(); + } + connectionFuture.complete(null); + } + + @Override + protected void onWebSocketMessage(WebSocket webSocket, String text) { + handleIncomingMessage(text); + } + + @Override + protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) {} + + @Override + protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { + readyState = WebSocketReadyState.CLOSED; + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException(t)); + } + connectionFuture.completeExceptionally(t); + } + + @Override + protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { + readyState = WebSocketReadyState.CLOSED; + if (onDisconnectedHandler != null) { + onDisconnectedHandler.accept(new DisconnectReason(code, reason)); + } + } + + @Override + protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, String reason) { + return super.shouldReconnectAfterClose(webSocket, code, reason) + && (code != 1005 || closeStreamSocket.get() != webSocket); + } + }; + reconnectingListener.connect(); + return connectionFuture; + } + + /** + * Disconnects the WebSocket connection and releases resources. + */ + public void disconnect() { + if (reconnectingListener != null) { + reconnectingListener.disconnect(); + } + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + } + + /** + * Gets the current state of the WebSocket connection. + * + * This provides the actual connection state, similar to the W3C WebSocket API. + * + * @return the current WebSocket ready state + */ + public WebSocketReadyState getReadyState() { + return readyState; + } + + /** + * Sends a ListenV2Media message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendMedia(ByteString message) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + // Use reconnecting listener's sendBinary method which handles queuing + reconnectingListener.sendBinary(message); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); + } + return future; + } + + /** + * Sends a ListenV2CloseStream message to the server asynchronously. A queued CloseStream does + * not suppress reconnects because it might never reach the server. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendCloseStream(ListenV2CloseStream message) { + return sendMessage(message, closeStreamSocket::set); + } + + /** + * Sends a ListenV2ForceEndTurn message to the server asynchronously. + * + *

Sending this with no turn in progress is not an error. The server answers with a + * {@code Warning} frame carrying code {@code FORCE_END_TURN_NO_ACTIVE_TURN} and ignores the + * request. Listen V2 has no typed warning event yet, so that frame is delivered as raw JSON + * to {@link #onMessage(java.util.function.Consumer)}. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendForceEndTurn(ListenV2ForceEndTurn message) { + return sendMessage(message); + } + + /** + * Sends a ListenV2Configure message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendConfigure(ListenV2Configure message) { + return sendMessage(message); + } + + /** + * Registers a handler for ListenV2Connected messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConnected(Consumer handler) { + this.connectedHandler = handler; + } + + /** + * Registers a handler for ListenV2TurnInfo messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onTurnInfo(Consumer handler) { + this.turnInfoHandler = handler; + } + + /** + * Registers a handler for ListenV2ConfigureSuccess messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConfigureSuccess(Consumer handler) { + this.configureSuccessHandler = handler; + } + + /** + * Registers a handler for ListenV2ConfigureFailure messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConfigureFailure(Consumer handler) { + this.configureFailureHandler = handler; + } + + /** + * Registers a handler for ListenV2FatalError messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onErrorMessage(Consumer handler) { + this.errorHandler = handler; + } + + /** + * Registers a handler called when the connection is established. + * @param handler the handler to invoke when connected + */ + public void onConnected(Runnable handler) { + this.onConnectedHandler = handler; + } + + /** + * Registers a handler called when the connection is closed. An empty peer close frame is + * reported with code 1005, even though the SDK acknowledges it with code 1000. + * @param handler the handler to invoke when disconnected + */ + public void onDisconnected(Consumer handler) { + this.onDisconnectedHandler = handler; + } + + /** + * Registers a handler called when an error occurs. + * @param handler the handler to invoke on error + */ + public void onError(Consumer handler) { + this.onErrorHandler = handler; + } + + /** + * Registers a handler called for every incoming text message. + * The handler receives the raw JSON string before type-specific dispatch. + * @param handler the handler to invoke with the raw message JSON + */ + public void onMessage(Consumer handler) { + this.onMessageHandler = handler; + } + + /** + * Configures reconnection behavior. Must be called before {@link #connect}. + * + * @param options the reconnection options (backoff, retries, queue size) + */ + public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { + this.reconnectOptions = options; + } + + /** + * Closes this WebSocket client, releasing all resources. + * Equivalent to calling {@link #disconnect()}. + */ + @Override + public void close() { + disconnect(); + } + + /** + * Ensures the WebSocket is connected and ready to send messages. + * @throws IllegalStateException if the socket is not connected or not open + */ + private void assertSocketIsOpen() { + if (reconnectingListener.getWebSocket() == null) { + throw new IllegalStateException("WebSocket is not connected. Call connect() first."); + } + if (readyState != WebSocketReadyState.OPEN) { + throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); + } + } + + private CompletableFuture sendMessage(Object body) { + return sendMessage(body, null); + } + + private CompletableFuture sendMessage(Object body, Consumer onSent) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + String json = objectMapper.writeValueAsString(body); + // Use reconnecting listener's send method which handles queuing + reconnectingListener.send(json, onSent); + future.complete(null); + } catch (IllegalStateException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send message", e)); + } + return future; + } + + private void handleIncomingMessage(String json) { + try { + if (onMessageHandler != null) { + onMessageHandler.accept(json); + } + JsonNode node = objectMapper.readTree(json); + if (node == null || node.isNull()) { + throw new IllegalArgumentException("Received null or invalid JSON message"); + } + if (node.has("request_id") + && node.has("sequence_id") + && node.has("event") + && node.has("turn_index") + && node.has("audio_window_start") + && node.has("audio_window_end") + && node.has("transcript") + && node.has("words") + && node.has("end_of_turn_confidence") + && "TurnInfo".equals(node.path("type").asText())) { + ListenV2TurnInfo turnInfoHandlerEvent = null; + try { + turnInfoHandlerEvent = objectMapper.treeToValue(node, ListenV2TurnInfo.class); + } catch (Exception e) { + } + if (turnInfoHandlerEvent != null) { + if (turnInfoHandler != null) { + turnInfoHandler.accept(turnInfoHandlerEvent); + } + return; + } + } + if (node.has("request_id") + && node.has("thresholds") + && node.has("keyterms") + && node.has("sequence_id") + && "ConfigureSuccess".equals(node.path("type").asText())) { + ListenV2ConfigureSuccess configureSuccessHandlerEvent = null; + try { + configureSuccessHandlerEvent = objectMapper.treeToValue(node, ListenV2ConfigureSuccess.class); + } catch (Exception e) { + } + if (configureSuccessHandlerEvent != null) { + if (configureSuccessHandler != null) { + configureSuccessHandler.accept(configureSuccessHandlerEvent); + } + return; + } + } + if (node.has("sequence_id") + && node.has("code") + && node.has("description") + && "Error".equals(node.path("type").asText())) { + ListenV2FatalError errorHandlerEvent = null; + try { + errorHandlerEvent = objectMapper.treeToValue(node, ListenV2FatalError.class); + } catch (Exception e) { + } + if (errorHandlerEvent != null) { + if (errorHandler != null) { + errorHandler.accept(errorHandlerEvent); + } + return; + } + } + if (node.has("request_id") + && node.has("sequence_id") + && "Connected".equals(node.path("type").asText())) { + ListenV2Connected connectedHandlerEvent = null; + try { + connectedHandlerEvent = objectMapper.treeToValue(node, ListenV2Connected.class); + } catch (Exception e) { + } + if (connectedHandlerEvent != null) { + if (connectedHandler != null) { + connectedHandler.accept(connectedHandlerEvent); + } + return; + } + } + if (node.has("request_id") + && node.has("sequence_id") + && "ConfigureFailure".equals(node.path("type").asText())) { + ListenV2ConfigureFailure configureFailureHandlerEvent = null; + try { + configureFailureHandlerEvent = objectMapper.treeToValue(node, ListenV2ConfigureFailure.class); + } catch (Exception e) { + } + if (configureFailureHandlerEvent != null) { + if (configureFailureHandler != null) { + configureFailureHandler.accept(configureFailureHandlerEvent); + } + return; + } + } + // The raw frame is already delivered to onMessage(String), so ignore unknown typed + // frames for forward compatibility with newly added server control messages. + } catch (Exception e) { + if (onErrorHandler != null) { + onErrorHandler.accept(e); + } + } + } +} diff --git a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak new file mode 100644 index 0000000..ae271f2 --- /dev/null +++ b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak @@ -0,0 +1,458 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.speak.v1.websocket; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ClientOptions; +import com.deepgram.core.DisconnectReason; +import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; +import com.deepgram.core.ReconnectingWebSocketListener; +import com.deepgram.core.RequestOptions; +import com.deepgram.core.WebSocketReadyState; +import com.deepgram.resources.speak.v1.types.SpeakV1Clear; +import com.deepgram.resources.speak.v1.types.SpeakV1Cleared; +import com.deepgram.resources.speak.v1.types.SpeakV1Close; +import com.deepgram.resources.speak.v1.types.SpeakV1Flush; +import com.deepgram.resources.speak.v1.types.SpeakV1Flushed; +import com.deepgram.resources.speak.v1.types.SpeakV1Metadata; +import com.deepgram.resources.speak.v1.types.SpeakV1Text; +import com.deepgram.resources.speak.v1.types.SpeakV1Warning; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okio.ByteString; + +/** + * WebSocket client for the v1 channel. + * Provides real-time bidirectional communication with strongly-typed messages. + */ +public class V1WebSocketClient implements AutoCloseable { + protected final ClientOptions clientOptions; + + private final ObjectMapper objectMapper; + + private final OkHttpClient okHttpClient; + + private ScheduledExecutorService timeoutExecutor; + + private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; + + private volatile Runnable onConnectedHandler; + + private volatile Consumer onDisconnectedHandler; + + private volatile Consumer onErrorHandler; + + private volatile Consumer onMessageHandler; + + private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; + + private CompletableFuture connectionFuture; + + private ReconnectingWebSocketListener reconnectingListener; + + private volatile Consumer speakV1AudioHandler; + + private volatile Consumer metadataHandler; + + private volatile Consumer flushedHandler; + + private volatile Consumer clearedHandler; + + private volatile Consumer warningHandler; + + /** + * Creates a new async WebSocket client for the v1 channel. + */ + public V1WebSocketClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.objectMapper = ObjectMappers.JSON_MAPPER; + this.okHttpClient = clientOptions.httpClient(); + } + + /** + * Establishes the WebSocket connection asynchronously with automatic reconnection. + * @return a CompletableFuture that completes when the connection is established + * @param options connection options including query parameters + */ + public CompletableFuture connect(V1ConnectOptions options) { + connectionFuture = new CompletableFuture<>(); + String baseUrl = clientOptions.environment().getProductionURL(); + String fullPath = "/v1/speak"; + if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { + fullPath = fullPath.substring(1); + } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { + fullPath = "/" + fullPath; + } + // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing + if (baseUrl.startsWith("wss://")) { + baseUrl = "https://" + baseUrl.substring(6); + } else if (baseUrl.startsWith("ws://")) { + baseUrl = "http://" + baseUrl.substring(5); + } + HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); + if (parsedUrl == null) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); + } + HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); + if (options.getEncoding() != null && options.getEncoding().isPresent()) { + urlBuilder.addQueryParameter( + "encoding", String.valueOf(options.getEncoding().get())); + } + if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { + urlBuilder.addQueryParameter( + "mip_opt_out", String.valueOf(options.getMipOptOut().get())); + } + if (options.getModel() != null && options.getModel().isPresent()) { + urlBuilder.addQueryParameter( + "model", String.valueOf(options.getModel().get())); + } + if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { + urlBuilder.addQueryParameter( + "sample_rate", String.valueOf(options.getSampleRate().get())); + } + if (options.getSpeed() != null && options.getSpeed().isPresent()) { + urlBuilder.addQueryParameter( + "speed", String.valueOf(options.getSpeed().get())); + } + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } + Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); + final Request request = requestBuilder.build(); + this.readyState = WebSocketReadyState.CONNECTING; + ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null + ? this.reconnectOptions + : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); + this.reconnectingListener = + new ReconnectingWebSocketListener(reconnectOpts, () -> { + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }) { + @Override + protected void onWebSocketOpen(WebSocket webSocket, Response response) { + readyState = WebSocketReadyState.OPEN; + if (onConnectedHandler != null) { + onConnectedHandler.run(); + } + connectionFuture.complete(null); + } + + @Override + protected void onWebSocketMessage(WebSocket webSocket, String text) { + handleIncomingMessage(text); + } + + @Override + protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { + if (speakV1AudioHandler != null) { + speakV1AudioHandler.accept(bytes); + } + } + + @Override + protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { + readyState = WebSocketReadyState.CLOSED; + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException(t)); + } + connectionFuture.completeExceptionally(t); + } + + @Override + protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { + readyState = WebSocketReadyState.CLOSED; + if (onDisconnectedHandler != null) { + onDisconnectedHandler.accept(new DisconnectReason(code, reason)); + } + } + }; + reconnectingListener.connect(); + return connectionFuture; + } + + /** + * Establishes the WebSocket connection asynchronously with default options. + * @return a CompletableFuture that completes when the connection is established + */ + public CompletableFuture connect() { + return connect(V1ConnectOptions.builder().build()); + } + + /** + * Disconnects the WebSocket connection and releases resources. + */ + public void disconnect() { + if (reconnectingListener != null) { + reconnectingListener.disconnect(); + } + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + } + + /** + * Gets the current state of the WebSocket connection. + * + * This provides the actual connection state, similar to the W3C WebSocket API. + * + * @return the current WebSocket ready state + */ + public WebSocketReadyState getReadyState() { + return readyState; + } + + /** + * Sends a SpeakV1Text message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendText(SpeakV1Text message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV1Flush message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendFlush(SpeakV1Flush message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV1Clear message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendClear(SpeakV1Clear message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV1Close message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendClose(SpeakV1Close message) { + return sendMessage(message); + } + + /** + * Registers a handler for SpeakV1Audio messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeakV1Audio(Consumer handler) { + this.speakV1AudioHandler = handler; + } + + /** + * Registers a handler for SpeakV1Metadata messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onMetadata(Consumer handler) { + this.metadataHandler = handler; + } + + /** + * Registers a handler for SpeakV1Flushed messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onFlushed(Consumer handler) { + this.flushedHandler = handler; + } + + /** + * Registers a handler for SpeakV1Cleared messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onCleared(Consumer handler) { + this.clearedHandler = handler; + } + + /** + * Registers a handler for SpeakV1Warning messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onWarning(Consumer handler) { + this.warningHandler = handler; + } + + /** + * Registers a handler called when the connection is established. + * @param handler the handler to invoke when connected + */ + public void onConnected(Runnable handler) { + this.onConnectedHandler = handler; + } + + /** + * Registers a handler called when the connection is closed. + * @param handler the handler to invoke when disconnected + */ + public void onDisconnected(Consumer handler) { + this.onDisconnectedHandler = handler; + } + + /** + * Registers a handler called when an error occurs. + * @param handler the handler to invoke on error + */ + public void onError(Consumer handler) { + this.onErrorHandler = handler; + } + + /** + * Registers a handler called for every incoming text message. + * The handler receives the raw JSON string before type-specific dispatch. + * @param handler the handler to invoke with the raw message JSON + */ + public void onMessage(Consumer handler) { + this.onMessageHandler = handler; + } + + /** + * Configures reconnection behavior. Must be called before {@link #connect}. + * + * @param options the reconnection options (backoff, retries, queue size) + */ + public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { + this.reconnectOptions = options; + } + + /** + * Closes this WebSocket client, releasing all resources. + * Equivalent to calling {@link #disconnect()}. + */ + @Override + public void close() { + disconnect(); + } + + /** + * Ensures the WebSocket is connected and ready to send messages. + * @throws IllegalStateException if the socket is not connected or not open + */ + private void assertSocketIsOpen() { + if (reconnectingListener.getWebSocket() == null) { + throw new IllegalStateException("WebSocket is not connected. Call connect() first."); + } + if (readyState != WebSocketReadyState.OPEN) { + throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); + } + } + + private CompletableFuture sendMessage(Object body) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + String json = objectMapper.writeValueAsString(body); + // Use reconnecting listener's send method which handles queuing + reconnectingListener.send(json); + future.complete(null); + } catch (IllegalStateException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send message", e)); + } + return future; + } + + private void handleIncomingMessage(String json) { + try { + if (onMessageHandler != null) { + onMessageHandler.accept(json); + } + JsonNode node = objectMapper.readTree(json); + if (node == null || node.isNull()) { + throw new IllegalArgumentException("Received null or invalid JSON message"); + } + if (node.has("request_id") + && node.has("model_name") + && node.has("model_version") + && node.has("model_uuid") + && "Metadata".equals(node.path("type").asText())) { + SpeakV1Metadata metadataHandlerEvent = null; + try { + metadataHandlerEvent = objectMapper.treeToValue(node, SpeakV1Metadata.class); + } catch (Exception e) { + } + if (metadataHandlerEvent != null) { + if (metadataHandler != null) { + metadataHandler.accept(metadataHandlerEvent); + } + return; + } + } + if (node.has("description") + && node.has("code") + && "Warning".equals(node.path("type").asText())) { + SpeakV1Warning warningHandlerEvent = null; + try { + warningHandlerEvent = objectMapper.treeToValue(node, SpeakV1Warning.class); + } catch (Exception e) { + } + if (warningHandlerEvent != null) { + if (warningHandler != null) { + warningHandler.accept(warningHandlerEvent); + } + return; + } + } + if (node.has("type") && node.has("sequence_id")) { + SpeakV1Flushed flushedHandlerEvent = null; + try { + flushedHandlerEvent = objectMapper.treeToValue(node, SpeakV1Flushed.class); + } catch (Exception e) { + } + if (flushedHandlerEvent != null) { + if (flushedHandler != null) { + flushedHandler.accept(flushedHandlerEvent); + } + return; + } + } + if (node.has("type") && node.has("sequence_id")) { + SpeakV1Cleared clearedHandlerEvent = null; + try { + clearedHandlerEvent = objectMapper.treeToValue(node, SpeakV1Cleared.class); + } catch (Exception e) { + } + if (clearedHandlerEvent != null) { + if (clearedHandler != null) { + clearedHandler.accept(clearedHandlerEvent); + } + return; + } + } + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException( + "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) + + "... Update your SDK version to support new message types.")); + } + } catch (Exception e) { + if (onErrorHandler != null) { + onErrorHandler.accept(e); + } + } + } +} diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak new file mode 100644 index 0000000..0479ff6 --- /dev/null +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.speak.v2.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = SpeakV2Close.Builder.class) +public final class SpeakV2Close { + private final Map additionalProperties; + + private SpeakV2Close(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "Close"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof SpeakV2Close; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(SpeakV2Close other) { + return this; + } + + public SpeakV2Close build() { + return new SpeakV2Close(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak new file mode 100644 index 0000000..db0b1ec --- /dev/null +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.speak.v2.types; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = SpeakV2Flush.Builder.class) +public final class SpeakV2Flush { + private final Map additionalProperties; + + private SpeakV2Flush(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "Flush"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof SpeakV2Flush; + } + + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(SpeakV2Flush other) { + return this; + } + + public SpeakV2Flush build() { + return new SpeakV2Flush(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak new file mode 100644 index 0000000..a94145d --- /dev/null +++ b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak @@ -0,0 +1,621 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.speak.v2.websocket; + +// Manual patch - see .fernignore. + +import com.deepgram.core.ClientOptions; +import com.deepgram.core.DisconnectReason; +import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; +import com.deepgram.core.ReconnectingWebSocketListener; +import com.deepgram.core.RequestOptions; +import com.deepgram.core.WebSocketReadyState; +import com.deepgram.resources.speak.v2.types.SpeakV2Close; +import com.deepgram.resources.speak.v2.types.SpeakV2Configure; +import com.deepgram.resources.speak.v2.types.SpeakV2ConfigureFailure; +import com.deepgram.resources.speak.v2.types.SpeakV2ConfigureSuccess; +import com.deepgram.resources.speak.v2.types.SpeakV2Connected; +import com.deepgram.resources.speak.v2.types.SpeakV2Error; +import com.deepgram.resources.speak.v2.types.SpeakV2Flush; +import com.deepgram.resources.speak.v2.types.SpeakV2Flushed; +import com.deepgram.resources.speak.v2.types.SpeakV2Interrupt; +import com.deepgram.resources.speak.v2.types.SpeakV2SessionMetadata; +import com.deepgram.resources.speak.v2.types.SpeakV2Speak; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechInterrupted; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechMetadata; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechStarted; +import com.deepgram.resources.speak.v2.types.SpeakV2Warning; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okio.ByteString; + +/** + * WebSocket client for the v2 channel. + * Provides real-time bidirectional communication with strongly-typed messages. + */ +public class V2WebSocketClient implements AutoCloseable { + protected final ClientOptions clientOptions; + + private final ObjectMapper objectMapper; + + private final OkHttpClient okHttpClient; + + private ScheduledExecutorService timeoutExecutor; + + private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; + + private volatile Runnable onConnectedHandler; + + private volatile Consumer onDisconnectedHandler; + + private volatile Consumer onErrorHandler; + + private volatile Consumer onMessageHandler; + + private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; + + private CompletableFuture connectionFuture; + + private ReconnectingWebSocketListener reconnectingListener; + + private volatile Consumer speakV2AudioHandler; + + private volatile Consumer connectedHandler; + + private volatile Consumer speechStartedHandler; + + private volatile Consumer speechMetadataHandler; + + private volatile Consumer speechInterruptedHandler; + + private volatile Consumer flushedHandler; + + private volatile Consumer sessionMetadataHandler; + + private volatile Consumer configureSuccessHandler; + + private volatile Consumer configureFailureHandler; + + private volatile Consumer warningHandler; + + private volatile Consumer errorHandler; + + /** + * Creates a new async WebSocket client for the v2 channel. + */ + public V2WebSocketClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.objectMapper = ObjectMappers.JSON_MAPPER; + this.okHttpClient = clientOptions.httpClient(); + } + + /** + * Establishes the WebSocket connection asynchronously with automatic reconnection. + * @return a CompletableFuture that completes when the connection is established + * @param options connection options including query parameters + */ + public CompletableFuture connect(V2ConnectOptions options) { + connectionFuture = new CompletableFuture<>(); + String baseUrl = clientOptions.environment().getProductionURL(); + String fullPath = "/v2/speak"; + if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { + fullPath = fullPath.substring(1); + } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { + fullPath = "/" + fullPath; + } + // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing + if (baseUrl.startsWith("wss://")) { + baseUrl = "https://" + baseUrl.substring(6); + } else if (baseUrl.startsWith("ws://")) { + baseUrl = "http://" + baseUrl.substring(5); + } + HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); + if (parsedUrl == null) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); + } + HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); + urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); + if (options.getEncoding() != null && options.getEncoding().isPresent()) { + urlBuilder.addQueryParameter( + "encoding", String.valueOf(options.getEncoding().get())); + } + if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { + urlBuilder.addQueryParameter( + "sample_rate", String.valueOf(options.getSampleRate().get())); + } + if (options.getSpeed() != null && options.getSpeed().isPresent()) { + urlBuilder.addQueryParameter( + "speed", String.valueOf(options.getSpeed().get())); + } + if (options.getExpressivity() != null && options.getExpressivity().isPresent()) { + urlBuilder.addQueryParameter( + "expressivity", String.valueOf(options.getExpressivity().get())); + } + if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { + urlBuilder.addQueryParameter( + "mip_opt_out", String.valueOf(options.getMipOptOut().get())); + } + if (options.getTag() != null && options.getTag().isPresent()) { + QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); + } + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } + Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); + final Request request = requestBuilder.build(); + this.readyState = WebSocketReadyState.CONNECTING; + ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null + ? this.reconnectOptions + : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); + this.reconnectingListener = + new ReconnectingWebSocketListener(reconnectOpts, () -> { + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }) { + @Override + protected void onWebSocketOpen(WebSocket webSocket, Response response) { + readyState = WebSocketReadyState.OPEN; + if (onConnectedHandler != null) { + onConnectedHandler.run(); + } + connectionFuture.complete(null); + } + + @Override + protected void onWebSocketMessage(WebSocket webSocket, String text) { + handleIncomingMessage(text); + } + + @Override + protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { + if (speakV2AudioHandler != null) { + speakV2AudioHandler.accept(bytes); + } + } + + @Override + protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { + readyState = WebSocketReadyState.CLOSED; + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException(t)); + } + connectionFuture.completeExceptionally(t); + } + + @Override + protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { + readyState = WebSocketReadyState.CLOSED; + if (onDisconnectedHandler != null) { + onDisconnectedHandler.accept(new DisconnectReason(code, reason)); + } + } + }; + reconnectingListener.connect(); + return connectionFuture; + } + + /** + * Disconnects the WebSocket connection and releases resources. + */ + public void disconnect() { + if (reconnectingListener != null) { + reconnectingListener.disconnect(); + } + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + } + + /** + * Gets the current state of the WebSocket connection. + * + * This provides the actual connection state, similar to the W3C WebSocket API. + * + * @return the current WebSocket ready state + */ + public WebSocketReadyState getReadyState() { + return readyState; + } + + /** + * Sends a SpeakV2Speak message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendSpeak(SpeakV2Speak message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV2Flush message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendFlush(SpeakV2Flush message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV2Interrupt message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendInterrupt(SpeakV2Interrupt message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV2Configure message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendConfigure(SpeakV2Configure message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV2Close message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendClose(SpeakV2Close message) { + return sendMessage(message); + } + + /** + * Registers a handler for SpeakV2Audio messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeakV2Audio(Consumer handler) { + this.speakV2AudioHandler = handler; + } + + /** + * Registers a handler for SpeakV2Connected messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConnected(Consumer handler) { + this.connectedHandler = handler; + } + + /** + * Registers a handler for SpeakV2SpeechStarted messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeechStarted(Consumer handler) { + this.speechStartedHandler = handler; + } + + /** + * Registers a handler for SpeakV2SpeechMetadata messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeechMetadata(Consumer handler) { + this.speechMetadataHandler = handler; + } + + /** + * Registers a handler for SpeakV2SpeechInterrupted messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeechInterrupted(Consumer handler) { + this.speechInterruptedHandler = handler; + } + + /** + * Registers a handler for SpeakV2Flushed messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onFlushed(Consumer handler) { + this.flushedHandler = handler; + } + + /** + * Registers a handler for SpeakV2SessionMetadata messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSessionMetadata(Consumer handler) { + this.sessionMetadataHandler = handler; + } + + /** + * Registers a handler for SpeakV2ConfigureSuccess messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConfigureSuccess(Consumer handler) { + this.configureSuccessHandler = handler; + } + + /** + * Registers a handler for SpeakV2ConfigureFailure messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConfigureFailure(Consumer handler) { + this.configureFailureHandler = handler; + } + + /** + * Registers a handler for SpeakV2Warning messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onWarning(Consumer handler) { + this.warningHandler = handler; + } + + /** + * Registers a handler for SpeakV2Error messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onErrorMessage(Consumer handler) { + this.errorHandler = handler; + } + + /** + * Registers a handler called when the connection is established. + * @param handler the handler to invoke when connected + */ + public void onConnected(Runnable handler) { + this.onConnectedHandler = handler; + } + + /** + * Registers a handler called when the connection is closed. + * @param handler the handler to invoke when disconnected + */ + public void onDisconnected(Consumer handler) { + this.onDisconnectedHandler = handler; + } + + /** + * Registers a handler called when an error occurs. + * @param handler the handler to invoke on error + */ + public void onError(Consumer handler) { + this.onErrorHandler = handler; + } + + /** + * Registers a handler called for every incoming text message. + * The handler receives the raw JSON string before type-specific dispatch. + * @param handler the handler to invoke with the raw message JSON + */ + public void onMessage(Consumer handler) { + this.onMessageHandler = handler; + } + + /** + * Configures reconnection behavior. Must be called before {@link #connect}. + * + * @param options the reconnection options (backoff, retries, queue size) + */ + public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { + this.reconnectOptions = options; + } + + /** + * Closes this WebSocket client, releasing all resources. + * Equivalent to calling {@link #disconnect()}. + */ + @Override + public void close() { + disconnect(); + } + + /** + * Ensures the WebSocket is connected and ready to send messages. + * @throws IllegalStateException if the socket is not connected or not open + */ + private void assertSocketIsOpen() { + if (reconnectingListener.getWebSocket() == null) { + throw new IllegalStateException("WebSocket is not connected. Call connect() first."); + } + if (readyState != WebSocketReadyState.OPEN) { + throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); + } + } + + private CompletableFuture sendMessage(Object body) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + String json = objectMapper.writeValueAsString(body); + // Use reconnecting listener's send method which handles queuing + reconnectingListener.send(json); + future.complete(null); + } catch (IllegalStateException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send message", e)); + } + return future; + } + + private void handleIncomingMessage(String json) { + try { + if (onMessageHandler != null) { + onMessageHandler.accept(json); + } + JsonNode node = objectMapper.readTree(json); + if (node == null || node.isNull()) { + throw new IllegalArgumentException("Received null or invalid JSON message"); + } + if (node.has("speech_id") + && node.has("audio_duration_ms") + && node.has("input_character_count") + && node.has("billable_character_count") + && node.has("controls_applied") + && "SpeechMetadata".equals(node.path("type").asText())) { + SpeakV2SpeechMetadata speechMetadataHandlerEvent = null; + try { + speechMetadataHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechMetadata.class); + } catch (Exception e) { + } + if (speechMetadataHandlerEvent != null) { + if (speechMetadataHandler != null) { + speechMetadataHandler.accept(speechMetadataHandlerEvent); + } + return; + } + } + if (node.has("request_id") + && node.has("model_name") + && node.has("model_version") + && node.has("model_uuids") + && "Connected".equals(node.path("type").asText())) { + SpeakV2Connected connectedHandlerEvent = null; + try { + connectedHandlerEvent = objectMapper.treeToValue(node, SpeakV2Connected.class); + } catch (Exception e) { + } + if (connectedHandlerEvent != null) { + if (connectedHandler != null) { + connectedHandler.accept(connectedHandlerEvent); + } + return; + } + } + if (node.has("total_audio_duration_ms") + && node.has("total_input_character_count") + && node.has("total_billable_character_count") + && "SessionMetadata".equals(node.path("type").asText())) { + SpeakV2SessionMetadata sessionMetadataHandlerEvent = null; + try { + sessionMetadataHandlerEvent = objectMapper.treeToValue(node, SpeakV2SessionMetadata.class); + } catch (Exception e) { + } + if (sessionMetadataHandlerEvent != null) { + if (sessionMetadataHandler != null) { + sessionMetadataHandler.accept(sessionMetadataHandlerEvent); + } + return; + } + } + if (node.has("audio_played_ms") + && node.has("metadata") + && "SpeechInterrupted".equals(node.path("type").asText())) { + SpeakV2SpeechInterrupted speechInterruptedHandlerEvent = null; + try { + speechInterruptedHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechInterrupted.class); + } catch (Exception e) { + } + if (speechInterruptedHandlerEvent != null) { + if (speechInterruptedHandler != null) { + speechInterruptedHandler.accept(speechInterruptedHandlerEvent); + } + return; + } + } + if (node.has("code") + && node.has("description") + && "ConfigureFailure".equals(node.path("type").asText())) { + SpeakV2ConfigureFailure configureFailureHandlerEvent = null; + try { + configureFailureHandlerEvent = objectMapper.treeToValue(node, SpeakV2ConfigureFailure.class); + } catch (Exception e) { + } + if (configureFailureHandlerEvent != null) { + if (configureFailureHandler != null) { + configureFailureHandler.accept(configureFailureHandlerEvent); + } + return; + } + } + if (node.has("code") + && node.has("description") + && "Warning".equals(node.path("type").asText())) { + SpeakV2Warning warningHandlerEvent = null; + try { + warningHandlerEvent = objectMapper.treeToValue(node, SpeakV2Warning.class); + } catch (Exception e) { + } + if (warningHandlerEvent != null) { + if (warningHandler != null) { + warningHandler.accept(warningHandlerEvent); + } + return; + } + } + if (node.has("code") + && node.has("description") + && "Error".equals(node.path("type").asText())) { + SpeakV2Error errorHandlerEvent = null; + try { + errorHandlerEvent = objectMapper.treeToValue(node, SpeakV2Error.class); + } catch (Exception e) { + } + if (errorHandlerEvent != null) { + if (errorHandler != null) { + errorHandler.accept(errorHandlerEvent); + } + return; + } + } + if (node.has("speech_id") + && "SpeechStarted".equals(node.path("type").asText())) { + SpeakV2SpeechStarted speechStartedHandlerEvent = null; + try { + speechStartedHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechStarted.class); + } catch (Exception e) { + } + if (speechStartedHandlerEvent != null) { + if (speechStartedHandler != null) { + speechStartedHandler.accept(speechStartedHandlerEvent); + } + return; + } + } + if (node.has("speech_id") && "Flushed".equals(node.path("type").asText())) { + SpeakV2Flushed flushedHandlerEvent = null; + try { + flushedHandlerEvent = objectMapper.treeToValue(node, SpeakV2Flushed.class); + } catch (Exception e) { + } + if (flushedHandlerEvent != null) { + if (flushedHandler != null) { + flushedHandler.accept(flushedHandlerEvent); + } + return; + } + } + if (node.has("applied") + && "ConfigureSuccess".equals(node.path("type").asText())) { + SpeakV2ConfigureSuccess configureSuccessHandlerEvent = null; + try { + configureSuccessHandlerEvent = objectMapper.treeToValue(node, SpeakV2ConfigureSuccess.class); + } catch (Exception e) { + } + if (configureSuccessHandlerEvent != null) { + if (configureSuccessHandler != null) { + configureSuccessHandler.accept(configureSuccessHandlerEvent); + } + return; + } + } + // The raw frame is already delivered to onMessage(String), so ignore unknown typed + // frames for forward compatibility with newly added server control messages. + } catch (Exception e) { + if (onErrorHandler != null) { + onErrorHandler.accept(e); + } + } + } +} diff --git a/src/main/java/com/deepgram/types/DeepgramModel.java.bak b/src/main/java/com/deepgram/types/DeepgramModel.java.bak new file mode 100644 index 0000000..14a797c --- /dev/null +++ b/src/main/java/com/deepgram/types/DeepgramModel.java.bak @@ -0,0 +1,1073 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.types; + +// Manual patch - see .fernignore. + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DeepgramModel { + public static final DeepgramModel AURA2SIRIO_ES = new DeepgramModel(Value.AURA2SIRIO_ES, "aura-2-sirio-es"); + + public static final DeepgramModel AURA2HERA_EN = new DeepgramModel(Value.AURA2HERA_EN, "aura-2-hera-en"); + + public static final DeepgramModel FLUX_KELSEY_EN = new DeepgramModel(Value.FLUX_KELSEY_EN, "flux-kelsey-en"); + + public static final DeepgramModel FLUX_RENEE_EN = new DeepgramModel(Value.FLUX_RENEE_EN, "flux-renee-en"); + + public static final DeepgramModel FLUX_SIENNA_EN = new DeepgramModel(Value.FLUX_SIENNA_EN, "flux-sienna-en"); + + public static final DeepgramModel AURA2HARMONIA_EN = + new DeepgramModel(Value.AURA2HARMONIA_EN, "aura-2-harmonia-en"); + + public static final DeepgramModel AURA2CARINA_ES = new DeepgramModel(Value.AURA2CARINA_ES, "aura-2-carina-es"); + + public static final DeepgramModel FLUX_MEENA_EN = new DeepgramModel(Value.FLUX_MEENA_EN, "flux-meena-en"); + + public static final DeepgramModel FLUX_MEGHAN_EN = new DeepgramModel(Value.FLUX_MEGHAN_EN, "flux-meghan-en"); + + public static final DeepgramModel AURA_ZEUS_EN = new DeepgramModel(Value.AURA_ZEUS_EN, "aura-zeus-en"); + + public static final DeepgramModel AURA2HERMES_EN = new DeepgramModel(Value.AURA2HERMES_EN, "aura-2-hermes-en"); + + public static final DeepgramModel FLUX_CONOR_EN = new DeepgramModel(Value.FLUX_CONOR_EN, "flux-conor-en"); + + public static final DeepgramModel AURA2NEPTUNE_EN = new DeepgramModel(Value.AURA2NEPTUNE_EN, "aura-2-neptune-en"); + + public static final DeepgramModel FLUX_BROOKE_EN = new DeepgramModel(Value.FLUX_BROOKE_EN, "flux-brooke-en"); + + public static final DeepgramModel FLUX_RUFUS_EN = new DeepgramModel(Value.FLUX_RUFUS_EN, "flux-rufus-en"); + + public static final DeepgramModel FLUX_WES_EN = new DeepgramModel(Value.FLUX_WES_EN, "flux-wes-en"); + + public static final DeepgramModel AURA2CALLISTA_EN = + new DeepgramModel(Value.AURA2CALLISTA_EN, "aura-2-callista-en"); + + public static final DeepgramModel AURA2AURORA_EN = new DeepgramModel(Value.AURA2AURORA_EN, "aura-2-aurora-en"); + + public static final DeepgramModel AURA2OPHELIA_EN = new DeepgramModel(Value.AURA2OPHELIA_EN, "aura-2-ophelia-en"); + + public static final DeepgramModel FLUX_BRUCE_EN = new DeepgramModel(Value.FLUX_BRUCE_EN, "flux-bruce-en"); + + public static final DeepgramModel AURA2APOLLO_EN = new DeepgramModel(Value.AURA2APOLLO_EN, "aura-2-apollo-en"); + + public static final DeepgramModel AURA2DELIA_EN = new DeepgramModel(Value.AURA2DELIA_EN, "aura-2-delia-en"); + + public static final DeepgramModel AURA2MARS_EN = new DeepgramModel(Value.AURA2MARS_EN, "aura-2-mars-en"); + + public static final DeepgramModel AURA_HERA_EN = new DeepgramModel(Value.AURA_HERA_EN, "aura-hera-en"); + + public static final DeepgramModel FLUX_MAEVE_EN = new DeepgramModel(Value.FLUX_MAEVE_EN, "flux-maeve-en"); + + public static final DeepgramModel FLUX_MARCUS_EN = new DeepgramModel(Value.FLUX_MARCUS_EN, "flux-marcus-en"); + + public static final DeepgramModel AURA2ARIES_EN = new DeepgramModel(Value.AURA2ARIES_EN, "aura-2-aries-en"); + + public static final DeepgramModel FLUX_PAIGE_EN = new DeepgramModel(Value.FLUX_PAIGE_EN, "flux-paige-en"); + + public static final DeepgramModel AURA2JUNO_EN = new DeepgramModel(Value.AURA2JUNO_EN, "aura-2-juno-en"); + + public static final DeepgramModel FLUX_DREW_EN = new DeepgramModel(Value.FLUX_DREW_EN, "flux-drew-en"); + + public static final DeepgramModel AURA2JAVIER_ES = new DeepgramModel(Value.AURA2JAVIER_ES, "aura-2-javier-es"); + + public static final DeepgramModel AURA2ANDROMEDA_EN = + new DeepgramModel(Value.AURA2ANDROMEDA_EN, "aura-2-andromeda-en"); + + public static final DeepgramModel AURA2SATURN_EN = new DeepgramModel(Value.AURA2SATURN_EN, "aura-2-saturn-en"); + + public static final DeepgramModel AURA2IRIS_EN = new DeepgramModel(Value.AURA2IRIS_EN, "aura-2-iris-en"); + + public static final DeepgramModel AURA_ANGUS_EN = new DeepgramModel(Value.AURA_ANGUS_EN, "aura-angus-en"); + + public static final DeepgramModel FLUX_KAI_EN = new DeepgramModel(Value.FLUX_KAI_EN, "flux-kai-en"); + + public static final DeepgramModel FLUX_BRITTANY_EN = new DeepgramModel(Value.FLUX_BRITTANY_EN, "flux-brittany-en"); + + public static final DeepgramModel FLUX_SEAN_EN = new DeepgramModel(Value.FLUX_SEAN_EN, "flux-sean-en"); + + public static final DeepgramModel AURA2HELENA_EN = new DeepgramModel(Value.AURA2HELENA_EN, "aura-2-helena-en"); + + public static final DeepgramModel FLUX_HALEY_EN = new DeepgramModel(Value.FLUX_HALEY_EN, "flux-haley-en"); + + public static final DeepgramModel AURA_STELLA_EN = new DeepgramModel(Value.AURA_STELLA_EN, "aura-stella-en"); + + public static final DeepgramModel FLUX_JACK_EN = new DeepgramModel(Value.FLUX_JACK_EN, "flux-jack-en"); + + public static final DeepgramModel AURA2DRACO_EN = new DeepgramModel(Value.AURA2DRACO_EN, "aura-2-draco-en"); + + public static final DeepgramModel FLUX_PRIYA_EN = new DeepgramModel(Value.FLUX_PRIYA_EN, "flux-priya-en"); + + public static final DeepgramModel AURA_HELIOS_EN = new DeepgramModel(Value.AURA_HELIOS_EN, "aura-helios-en"); + + public static final DeepgramModel AURA2JANUS_EN = new DeepgramModel(Value.AURA2JANUS_EN, "aura-2-janus-en"); + + public static final DeepgramModel AURA2NESTOR_ES = new DeepgramModel(Value.AURA2NESTOR_ES, "aura-2-nestor-es"); + + public static final DeepgramModel AURA2ORION_EN = new DeepgramModel(Value.AURA2ORION_EN, "aura-2-orion-en"); + + public static final DeepgramModel AURA2MINERVA_EN = new DeepgramModel(Value.AURA2MINERVA_EN, "aura-2-minerva-en"); + + public static final DeepgramModel AURA2ALVARO_ES = new DeepgramModel(Value.AURA2ALVARO_ES, "aura-2-alvaro-es"); + + public static final DeepgramModel AURA_PERSEUS_EN = new DeepgramModel(Value.AURA_PERSEUS_EN, "aura-perseus-en"); + + public static final DeepgramModel AURA2VESTA_EN = new DeepgramModel(Value.AURA2VESTA_EN, "aura-2-vesta-en"); + + public static final DeepgramModel FLUX_COLE_EN = new DeepgramModel(Value.FLUX_COLE_EN, "flux-cole-en"); + + public static final DeepgramModel AURA2ZEUS_EN = new DeepgramModel(Value.AURA2ZEUS_EN, "aura-2-zeus-en"); + + public static final DeepgramModel AURA2ORPHEUS_EN = new DeepgramModel(Value.AURA2ORPHEUS_EN, "aura-2-orpheus-en"); + + public static final DeepgramModel AURA2THALIA_EN = new DeepgramModel(Value.AURA2THALIA_EN, "aura-2-thalia-en"); + + public static final DeepgramModel FLUX_NAVEEN_EN = new DeepgramModel(Value.FLUX_NAVEEN_EN, "flux-naveen-en"); + + public static final DeepgramModel AURA2ASTERIA_EN = new DeepgramModel(Value.AURA2ASTERIA_EN, "aura-2-asteria-en"); + + public static final DeepgramModel AURA2SELENA_ES = new DeepgramModel(Value.AURA2SELENA_ES, "aura-2-selena-es"); + + public static final DeepgramModel FLUX_MARCELO_EN = new DeepgramModel(Value.FLUX_MARCELO_EN, "flux-marcelo-en"); + + public static final DeepgramModel FLUX_ELISE_EN = new DeepgramModel(Value.FLUX_ELISE_EN, "flux-elise-en"); + + public static final DeepgramModel FLUX_ALEXIS_EN = new DeepgramModel(Value.FLUX_ALEXIS_EN, "flux-alexis-en"); + + public static final DeepgramModel AURA_LUNA_EN = new DeepgramModel(Value.AURA_LUNA_EN, "aura-luna-en"); + + public static final DeepgramModel AURA_ORPHEUS_EN = new DeepgramModel(Value.AURA_ORPHEUS_EN, "aura-orpheus-en"); + + public static final DeepgramModel AURA2AMALTHEA_EN = + new DeepgramModel(Value.AURA2AMALTHEA_EN, "aura-2-amalthea-en"); + + public static final DeepgramModel FLUX_DONOVAN_EN = new DeepgramModel(Value.FLUX_DONOVAN_EN, "flux-donovan-en"); + + public static final DeepgramModel AURA2SELENE_EN = new DeepgramModel(Value.AURA2SELENE_EN, "aura-2-selene-en"); + + public static final DeepgramModel FLUX_TANNER_EN = new DeepgramModel(Value.FLUX_TANNER_EN, "flux-tanner-en"); + + public static final DeepgramModel FLUX_SHARON_EN = new DeepgramModel(Value.FLUX_SHARON_EN, "flux-sharon-en"); + + public static final DeepgramModel AURA2LUNA_EN = new DeepgramModel(Value.AURA2LUNA_EN, "aura-2-luna-en"); + + public static final DeepgramModel AURA2PHOEBE_EN = new DeepgramModel(Value.AURA2PHOEBE_EN, "aura-2-phoebe-en"); + + public static final DeepgramModel FLUX_HANNAH_EN = new DeepgramModel(Value.FLUX_HANNAH_EN, "flux-hannah-en"); + + public static final DeepgramModel AURA2DIANA_ES = new DeepgramModel(Value.AURA2DIANA_ES, "aura-2-diana-es"); + + public static final DeepgramModel AURA_ORION_EN = new DeepgramModel(Value.AURA_ORION_EN, "aura-orion-en"); + + public static final DeepgramModel AURA2ATHENA_EN = new DeepgramModel(Value.AURA2ATHENA_EN, "aura-2-athena-en"); + + public static final DeepgramModel AURA_ARCAS_EN = new DeepgramModel(Value.AURA_ARCAS_EN, "aura-arcas-en"); + + public static final DeepgramModel AURA2THEIA_EN = new DeepgramModel(Value.AURA2THEIA_EN, "aura-2-theia-en"); + + public static final DeepgramModel FLUX_WADE_EN = new DeepgramModel(Value.FLUX_WADE_EN, "flux-wade-en"); + + public static final DeepgramModel FLUX_HEATHER_EN = new DeepgramModel(Value.FLUX_HEATHER_EN, "flux-heather-en"); + + public static final DeepgramModel FLUX_CLIFF_EN = new DeepgramModel(Value.FLUX_CLIFF_EN, "flux-cliff-en"); + + public static final DeepgramModel AURA2JUPITER_EN = new DeepgramModel(Value.AURA2JUPITER_EN, "aura-2-jupiter-en"); + + public static final DeepgramModel FLUX_KIT_EN = new DeepgramModel(Value.FLUX_KIT_EN, "flux-kit-en"); + + public static final DeepgramModel AURA2AQUILA_ES = new DeepgramModel(Value.AURA2AQUILA_ES, "aura-2-aquila-es"); + + public static final DeepgramModel AURA2CORA_EN = new DeepgramModel(Value.AURA2CORA_EN, "aura-2-cora-en"); + + public static final DeepgramModel AURA2CORDELIA_EN = + new DeepgramModel(Value.AURA2CORDELIA_EN, "aura-2-cordelia-en"); + + public static final DeepgramModel AURA2ATLAS_EN = new DeepgramModel(Value.AURA2ATLAS_EN, "aura-2-atlas-en"); + + public static final DeepgramModel AURA2HYPERION_EN = + new DeepgramModel(Value.AURA2HYPERION_EN, "aura-2-hyperion-en"); + + public static final DeepgramModel AURA2CELESTE_ES = new DeepgramModel(Value.AURA2CELESTE_ES, "aura-2-celeste-es"); + + public static final DeepgramModel AURA2PLUTO_EN = new DeepgramModel(Value.AURA2PLUTO_EN, "aura-2-pluto-en"); + + public static final DeepgramModel FLUX_BREE_EN = new DeepgramModel(Value.FLUX_BREE_EN, "flux-bree-en"); + + public static final DeepgramModel AURA2ARCAS_EN = new DeepgramModel(Value.AURA2ARCAS_EN, "aura-2-arcas-en"); + + public static final DeepgramModel FLUX_COLIN_EN = new DeepgramModel(Value.FLUX_COLIN_EN, "flux-colin-en"); + + public static final DeepgramModel AURA_ATHENA_EN = new DeepgramModel(Value.AURA_ATHENA_EN, "aura-athena-en"); + + public static final DeepgramModel AURA2ODYSSEUS_EN = + new DeepgramModel(Value.AURA2ODYSSEUS_EN, "aura-2-odysseus-en"); + + public static final DeepgramModel AURA2PANDORA_EN = new DeepgramModel(Value.AURA2PANDORA_EN, "aura-2-pandora-en"); + + public static final DeepgramModel FLUX_MILES_EN = new DeepgramModel(Value.FLUX_MILES_EN, "flux-miles-en"); + + public static final DeepgramModel AURA_ASTERIA_EN = new DeepgramModel(Value.AURA_ASTERIA_EN, "aura-asteria-en"); + + public static final DeepgramModel AURA2ELECTRA_EN = new DeepgramModel(Value.AURA2ELECTRA_EN, "aura-2-electra-en"); + + public static final DeepgramModel FLUX_GEMMA_EN = new DeepgramModel(Value.FLUX_GEMMA_EN, "flux-gemma-en"); + + public static final DeepgramModel AURA2ESTRELLA_ES = + new DeepgramModel(Value.AURA2ESTRELLA_ES, "aura-2-estrella-es"); + + private final Value value; + + private final String string; + + DeepgramModel(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DeepgramModel && this.string.equals(((DeepgramModel) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case AURA2SIRIO_ES: + return visitor.visitAura2SirioEs(); + case AURA2HERA_EN: + return visitor.visitAura2HeraEn(); + case FLUX_KELSEY_EN: + return visitor.visitFluxKelseyEn(); + case FLUX_RENEE_EN: + return visitor.visitFluxReneeEn(); + case FLUX_SIENNA_EN: + return visitor.visitFluxSiennaEn(); + case AURA2HARMONIA_EN: + return visitor.visitAura2HarmoniaEn(); + case AURA2CARINA_ES: + return visitor.visitAura2CarinaEs(); + case FLUX_MEENA_EN: + return visitor.visitFluxMeenaEn(); + case FLUX_MEGHAN_EN: + return visitor.visitFluxMeghanEn(); + case AURA_ZEUS_EN: + return visitor.visitAuraZeusEn(); + case AURA2HERMES_EN: + return visitor.visitAura2HermesEn(); + case FLUX_CONOR_EN: + return visitor.visitFluxConorEn(); + case AURA2NEPTUNE_EN: + return visitor.visitAura2NeptuneEn(); + case FLUX_BROOKE_EN: + return visitor.visitFluxBrookeEn(); + case FLUX_RUFUS_EN: + return visitor.visitFluxRufusEn(); + case FLUX_WES_EN: + return visitor.visitFluxWesEn(); + case AURA2CALLISTA_EN: + return visitor.visitAura2CallistaEn(); + case AURA2AURORA_EN: + return visitor.visitAura2AuroraEn(); + case AURA2OPHELIA_EN: + return visitor.visitAura2OpheliaEn(); + case FLUX_BRUCE_EN: + return visitor.visitFluxBruceEn(); + case AURA2APOLLO_EN: + return visitor.visitAura2ApolloEn(); + case AURA2DELIA_EN: + return visitor.visitAura2DeliaEn(); + case AURA2MARS_EN: + return visitor.visitAura2MarsEn(); + case AURA_HERA_EN: + return visitor.visitAuraHeraEn(); + case FLUX_MAEVE_EN: + return visitor.visitFluxMaeveEn(); + case FLUX_MARCUS_EN: + return visitor.visitFluxMarcusEn(); + case AURA2ARIES_EN: + return visitor.visitAura2AriesEn(); + case FLUX_PAIGE_EN: + return visitor.visitFluxPaigeEn(); + case AURA2JUNO_EN: + return visitor.visitAura2JunoEn(); + case FLUX_DREW_EN: + return visitor.visitFluxDrewEn(); + case AURA2JAVIER_ES: + return visitor.visitAura2JavierEs(); + case AURA2ANDROMEDA_EN: + return visitor.visitAura2AndromedaEn(); + case AURA2SATURN_EN: + return visitor.visitAura2SaturnEn(); + case AURA2IRIS_EN: + return visitor.visitAura2IrisEn(); + case AURA_ANGUS_EN: + return visitor.visitAuraAngusEn(); + case FLUX_KAI_EN: + return visitor.visitFluxKaiEn(); + case FLUX_BRITTANY_EN: + return visitor.visitFluxBrittanyEn(); + case FLUX_SEAN_EN: + return visitor.visitFluxSeanEn(); + case AURA2HELENA_EN: + return visitor.visitAura2HelenaEn(); + case FLUX_HALEY_EN: + return visitor.visitFluxHaleyEn(); + case AURA_STELLA_EN: + return visitor.visitAuraStellaEn(); + case FLUX_JACK_EN: + return visitor.visitFluxJackEn(); + case AURA2DRACO_EN: + return visitor.visitAura2DracoEn(); + case FLUX_PRIYA_EN: + return visitor.visitFluxPriyaEn(); + case AURA_HELIOS_EN: + return visitor.visitAuraHeliosEn(); + case AURA2JANUS_EN: + return visitor.visitAura2JanusEn(); + case AURA2NESTOR_ES: + return visitor.visitAura2NestorEs(); + case AURA2ORION_EN: + return visitor.visitAura2OrionEn(); + case AURA2MINERVA_EN: + return visitor.visitAura2MinervaEn(); + case AURA2ALVARO_ES: + return visitor.visitAura2AlvaroEs(); + case AURA_PERSEUS_EN: + return visitor.visitAuraPerseusEn(); + case AURA2VESTA_EN: + return visitor.visitAura2VestaEn(); + case FLUX_COLE_EN: + return visitor.visitFluxColeEn(); + case AURA2ZEUS_EN: + return visitor.visitAura2ZeusEn(); + case AURA2ORPHEUS_EN: + return visitor.visitAura2OrpheusEn(); + case AURA2THALIA_EN: + return visitor.visitAura2ThaliaEn(); + case FLUX_NAVEEN_EN: + return visitor.visitFluxNaveenEn(); + case AURA2ASTERIA_EN: + return visitor.visitAura2AsteriaEn(); + case AURA2SELENA_ES: + return visitor.visitAura2SelenaEs(); + case FLUX_MARCELO_EN: + return visitor.visitFluxMarceloEn(); + case FLUX_ELISE_EN: + return visitor.visitFluxEliseEn(); + case FLUX_ALEXIS_EN: + return visitor.visitFluxAlexisEn(); + case AURA_LUNA_EN: + return visitor.visitAuraLunaEn(); + case AURA_ORPHEUS_EN: + return visitor.visitAuraOrpheusEn(); + case AURA2AMALTHEA_EN: + return visitor.visitAura2AmaltheaEn(); + case FLUX_DONOVAN_EN: + return visitor.visitFluxDonovanEn(); + case AURA2SELENE_EN: + return visitor.visitAura2SeleneEn(); + case FLUX_TANNER_EN: + return visitor.visitFluxTannerEn(); + case FLUX_SHARON_EN: + return visitor.visitFluxSharonEn(); + case AURA2LUNA_EN: + return visitor.visitAura2LunaEn(); + case AURA2PHOEBE_EN: + return visitor.visitAura2PhoebeEn(); + case FLUX_HANNAH_EN: + return visitor.visitFluxHannahEn(); + case AURA2DIANA_ES: + return visitor.visitAura2DianaEs(); + case AURA_ORION_EN: + return visitor.visitAuraOrionEn(); + case AURA2ATHENA_EN: + return visitor.visitAura2AthenaEn(); + case AURA_ARCAS_EN: + return visitor.visitAuraArcasEn(); + case AURA2THEIA_EN: + return visitor.visitAura2TheiaEn(); + case FLUX_WADE_EN: + return visitor.visitFluxWadeEn(); + case FLUX_HEATHER_EN: + return visitor.visitFluxHeatherEn(); + case FLUX_CLIFF_EN: + return visitor.visitFluxCliffEn(); + case AURA2JUPITER_EN: + return visitor.visitAura2JupiterEn(); + case FLUX_KIT_EN: + return visitor.visitFluxKitEn(); + case AURA2AQUILA_ES: + return visitor.visitAura2AquilaEs(); + case AURA2CORA_EN: + return visitor.visitAura2CoraEn(); + case AURA2CORDELIA_EN: + return visitor.visitAura2CordeliaEn(); + case AURA2ATLAS_EN: + return visitor.visitAura2AtlasEn(); + case AURA2HYPERION_EN: + return visitor.visitAura2HyperionEn(); + case AURA2CELESTE_ES: + return visitor.visitAura2CelesteEs(); + case AURA2PLUTO_EN: + return visitor.visitAura2PlutoEn(); + case FLUX_BREE_EN: + return visitor.visitFluxBreeEn(); + case AURA2ARCAS_EN: + return visitor.visitAura2ArcasEn(); + case FLUX_COLIN_EN: + return visitor.visitFluxColinEn(); + case AURA_ATHENA_EN: + return visitor.visitAuraAthenaEn(); + case AURA2ODYSSEUS_EN: + return visitor.visitAura2OdysseusEn(); + case AURA2PANDORA_EN: + return visitor.visitAura2PandoraEn(); + case FLUX_MILES_EN: + return visitor.visitFluxMilesEn(); + case AURA_ASTERIA_EN: + return visitor.visitAuraAsteriaEn(); + case AURA2ELECTRA_EN: + return visitor.visitAura2ElectraEn(); + case FLUX_GEMMA_EN: + return visitor.visitFluxGemmaEn(); + case AURA2ESTRELLA_ES: + return visitor.visitAura2EstrellaEs(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DeepgramModel valueOf(String value) { + switch (value) { + case "aura-2-sirio-es": + return AURA2SIRIO_ES; + case "aura-2-hera-en": + return AURA2HERA_EN; + case "flux-kelsey-en": + return FLUX_KELSEY_EN; + case "flux-renee-en": + return FLUX_RENEE_EN; + case "flux-sienna-en": + return FLUX_SIENNA_EN; + case "aura-2-harmonia-en": + return AURA2HARMONIA_EN; + case "aura-2-carina-es": + return AURA2CARINA_ES; + case "flux-meena-en": + return FLUX_MEENA_EN; + case "flux-meghan-en": + return FLUX_MEGHAN_EN; + case "aura-zeus-en": + return AURA_ZEUS_EN; + case "aura-2-hermes-en": + return AURA2HERMES_EN; + case "flux-conor-en": + return FLUX_CONOR_EN; + case "aura-2-neptune-en": + return AURA2NEPTUNE_EN; + case "flux-brooke-en": + return FLUX_BROOKE_EN; + case "flux-rufus-en": + return FLUX_RUFUS_EN; + case "flux-wes-en": + return FLUX_WES_EN; + case "aura-2-callista-en": + return AURA2CALLISTA_EN; + case "aura-2-aurora-en": + return AURA2AURORA_EN; + case "aura-2-ophelia-en": + return AURA2OPHELIA_EN; + case "flux-bruce-en": + return FLUX_BRUCE_EN; + case "aura-2-apollo-en": + return AURA2APOLLO_EN; + case "aura-2-delia-en": + return AURA2DELIA_EN; + case "aura-2-mars-en": + return AURA2MARS_EN; + case "aura-hera-en": + return AURA_HERA_EN; + case "flux-maeve-en": + return FLUX_MAEVE_EN; + case "flux-marcus-en": + return FLUX_MARCUS_EN; + case "aura-2-aries-en": + return AURA2ARIES_EN; + case "flux-paige-en": + return FLUX_PAIGE_EN; + case "aura-2-juno-en": + return AURA2JUNO_EN; + case "flux-drew-en": + return FLUX_DREW_EN; + case "aura-2-javier-es": + return AURA2JAVIER_ES; + case "aura-2-andromeda-en": + return AURA2ANDROMEDA_EN; + case "aura-2-saturn-en": + return AURA2SATURN_EN; + case "aura-2-iris-en": + return AURA2IRIS_EN; + case "aura-angus-en": + return AURA_ANGUS_EN; + case "flux-kai-en": + return FLUX_KAI_EN; + case "flux-brittany-en": + return FLUX_BRITTANY_EN; + case "flux-sean-en": + return FLUX_SEAN_EN; + case "aura-2-helena-en": + return AURA2HELENA_EN; + case "flux-haley-en": + return FLUX_HALEY_EN; + case "aura-stella-en": + return AURA_STELLA_EN; + case "flux-jack-en": + return FLUX_JACK_EN; + case "aura-2-draco-en": + return AURA2DRACO_EN; + case "flux-priya-en": + return FLUX_PRIYA_EN; + case "aura-helios-en": + return AURA_HELIOS_EN; + case "aura-2-janus-en": + return AURA2JANUS_EN; + case "aura-2-nestor-es": + return AURA2NESTOR_ES; + case "aura-2-orion-en": + return AURA2ORION_EN; + case "aura-2-minerva-en": + return AURA2MINERVA_EN; + case "aura-2-alvaro-es": + return AURA2ALVARO_ES; + case "aura-perseus-en": + return AURA_PERSEUS_EN; + case "aura-2-vesta-en": + return AURA2VESTA_EN; + case "flux-cole-en": + return FLUX_COLE_EN; + case "aura-2-zeus-en": + return AURA2ZEUS_EN; + case "aura-2-orpheus-en": + return AURA2ORPHEUS_EN; + case "aura-2-thalia-en": + return AURA2THALIA_EN; + case "flux-naveen-en": + return FLUX_NAVEEN_EN; + case "aura-2-asteria-en": + return AURA2ASTERIA_EN; + case "aura-2-selena-es": + return AURA2SELENA_ES; + case "flux-marcelo-en": + return FLUX_MARCELO_EN; + case "flux-elise-en": + return FLUX_ELISE_EN; + case "flux-alexis-en": + return FLUX_ALEXIS_EN; + case "aura-luna-en": + return AURA_LUNA_EN; + case "aura-orpheus-en": + return AURA_ORPHEUS_EN; + case "aura-2-amalthea-en": + return AURA2AMALTHEA_EN; + case "flux-donovan-en": + return FLUX_DONOVAN_EN; + case "aura-2-selene-en": + return AURA2SELENE_EN; + case "flux-tanner-en": + return FLUX_TANNER_EN; + case "flux-sharon-en": + return FLUX_SHARON_EN; + case "aura-2-luna-en": + return AURA2LUNA_EN; + case "aura-2-phoebe-en": + return AURA2PHOEBE_EN; + case "flux-hannah-en": + return FLUX_HANNAH_EN; + case "aura-2-diana-es": + return AURA2DIANA_ES; + case "aura-orion-en": + return AURA_ORION_EN; + case "aura-2-athena-en": + return AURA2ATHENA_EN; + case "aura-arcas-en": + return AURA_ARCAS_EN; + case "aura-2-theia-en": + return AURA2THEIA_EN; + case "flux-wade-en": + return FLUX_WADE_EN; + case "flux-heather-en": + return FLUX_HEATHER_EN; + case "flux-cliff-en": + return FLUX_CLIFF_EN; + case "aura-2-jupiter-en": + return AURA2JUPITER_EN; + case "flux-kit-en": + return FLUX_KIT_EN; + case "aura-2-aquila-es": + return AURA2AQUILA_ES; + case "aura-2-cora-en": + return AURA2CORA_EN; + case "aura-2-cordelia-en": + return AURA2CORDELIA_EN; + case "aura-2-atlas-en": + return AURA2ATLAS_EN; + case "aura-2-hyperion-en": + return AURA2HYPERION_EN; + case "aura-2-celeste-es": + return AURA2CELESTE_ES; + case "aura-2-pluto-en": + return AURA2PLUTO_EN; + case "flux-bree-en": + return FLUX_BREE_EN; + case "aura-2-arcas-en": + return AURA2ARCAS_EN; + case "flux-colin-en": + return FLUX_COLIN_EN; + case "aura-athena-en": + return AURA_ATHENA_EN; + case "aura-2-odysseus-en": + return AURA2ODYSSEUS_EN; + case "aura-2-pandora-en": + return AURA2PANDORA_EN; + case "flux-miles-en": + return FLUX_MILES_EN; + case "aura-asteria-en": + return AURA_ASTERIA_EN; + case "aura-2-electra-en": + return AURA2ELECTRA_EN; + case "flux-gemma-en": + return FLUX_GEMMA_EN; + case "aura-2-estrella-es": + return AURA2ESTRELLA_ES; + default: + return new DeepgramModel(Value.UNKNOWN, value); + } + } + + public enum Value { + AURA_ASTERIA_EN, + + AURA_LUNA_EN, + + AURA_STELLA_EN, + + AURA_ATHENA_EN, + + AURA_HERA_EN, + + AURA_ORION_EN, + + AURA_ARCAS_EN, + + AURA_PERSEUS_EN, + + AURA_ANGUS_EN, + + AURA_ORPHEUS_EN, + + AURA_HELIOS_EN, + + AURA_ZEUS_EN, + + AURA2AMALTHEA_EN, + + AURA2ANDROMEDA_EN, + + AURA2APOLLO_EN, + + AURA2ARCAS_EN, + + AURA2ARIES_EN, + + AURA2ASTERIA_EN, + + AURA2ATHENA_EN, + + AURA2ATLAS_EN, + + AURA2AURORA_EN, + + AURA2CALLISTA_EN, + + AURA2CORA_EN, + + AURA2CORDELIA_EN, + + AURA2DELIA_EN, + + AURA2DRACO_EN, + + AURA2ELECTRA_EN, + + AURA2HARMONIA_EN, + + AURA2HELENA_EN, + + AURA2HERA_EN, + + AURA2HERMES_EN, + + AURA2HYPERION_EN, + + AURA2IRIS_EN, + + AURA2JANUS_EN, + + AURA2JUNO_EN, + + AURA2JUPITER_EN, + + AURA2LUNA_EN, + + AURA2MARS_EN, + + AURA2MINERVA_EN, + + AURA2NEPTUNE_EN, + + AURA2ODYSSEUS_EN, + + AURA2OPHELIA_EN, + + AURA2ORION_EN, + + AURA2ORPHEUS_EN, + + AURA2PANDORA_EN, + + AURA2PHOEBE_EN, + + AURA2PLUTO_EN, + + AURA2SATURN_EN, + + AURA2SELENE_EN, + + AURA2THALIA_EN, + + AURA2THEIA_EN, + + AURA2VESTA_EN, + + AURA2ZEUS_EN, + + AURA2SIRIO_ES, + + AURA2NESTOR_ES, + + AURA2CARINA_ES, + + AURA2CELESTE_ES, + + AURA2ALVARO_ES, + + AURA2DIANA_ES, + + AURA2AQUILA_ES, + + AURA2SELENA_ES, + + AURA2ESTRELLA_ES, + + AURA2JAVIER_ES, + + FLUX_ALEXIS_EN, + + FLUX_BREE_EN, + + FLUX_BRITTANY_EN, + + FLUX_BROOKE_EN, + + FLUX_BRUCE_EN, + + FLUX_CLIFF_EN, + + FLUX_COLE_EN, + + FLUX_COLIN_EN, + + FLUX_CONOR_EN, + + FLUX_DONOVAN_EN, + + FLUX_DREW_EN, + + FLUX_ELISE_EN, + + FLUX_GEMMA_EN, + + FLUX_HALEY_EN, + + FLUX_HANNAH_EN, + + FLUX_HEATHER_EN, + + FLUX_JACK_EN, + + FLUX_KAI_EN, + + FLUX_KELSEY_EN, + + FLUX_KIT_EN, + + FLUX_MAEVE_EN, + + FLUX_MARCELO_EN, + + FLUX_MARCUS_EN, + + FLUX_MEENA_EN, + + FLUX_MEGHAN_EN, + + FLUX_MILES_EN, + + FLUX_NAVEEN_EN, + + FLUX_PAIGE_EN, + + FLUX_PRIYA_EN, + + FLUX_RENEE_EN, + + FLUX_RUFUS_EN, + + FLUX_SEAN_EN, + + FLUX_SHARON_EN, + + FLUX_SIENNA_EN, + + FLUX_TANNER_EN, + + FLUX_WADE_EN, + + FLUX_WES_EN, + + UNKNOWN + } + + public interface Visitor { + T visitAuraAsteriaEn(); + + T visitAuraLunaEn(); + + T visitAuraStellaEn(); + + T visitAuraAthenaEn(); + + T visitAuraHeraEn(); + + T visitAuraOrionEn(); + + T visitAuraArcasEn(); + + T visitAuraPerseusEn(); + + T visitAuraAngusEn(); + + T visitAuraOrpheusEn(); + + T visitAuraHeliosEn(); + + T visitAuraZeusEn(); + + T visitAura2AmaltheaEn(); + + T visitAura2AndromedaEn(); + + T visitAura2ApolloEn(); + + T visitAura2ArcasEn(); + + T visitAura2AriesEn(); + + T visitAura2AsteriaEn(); + + T visitAura2AthenaEn(); + + T visitAura2AtlasEn(); + + T visitAura2AuroraEn(); + + T visitAura2CallistaEn(); + + T visitAura2CoraEn(); + + T visitAura2CordeliaEn(); + + T visitAura2DeliaEn(); + + T visitAura2DracoEn(); + + T visitAura2ElectraEn(); + + T visitAura2HarmoniaEn(); + + T visitAura2HelenaEn(); + + T visitAura2HeraEn(); + + T visitAura2HermesEn(); + + T visitAura2HyperionEn(); + + T visitAura2IrisEn(); + + T visitAura2JanusEn(); + + T visitAura2JunoEn(); + + T visitAura2JupiterEn(); + + T visitAura2LunaEn(); + + T visitAura2MarsEn(); + + T visitAura2MinervaEn(); + + T visitAura2NeptuneEn(); + + T visitAura2OdysseusEn(); + + T visitAura2OpheliaEn(); + + T visitAura2OrionEn(); + + T visitAura2OrpheusEn(); + + T visitAura2PandoraEn(); + + T visitAura2PhoebeEn(); + + T visitAura2PlutoEn(); + + T visitAura2SaturnEn(); + + T visitAura2SeleneEn(); + + T visitAura2ThaliaEn(); + + T visitAura2TheiaEn(); + + T visitAura2VestaEn(); + + T visitAura2ZeusEn(); + + T visitAura2SirioEs(); + + T visitAura2NestorEs(); + + T visitAura2CarinaEs(); + + T visitAura2CelesteEs(); + + T visitAura2AlvaroEs(); + + T visitAura2DianaEs(); + + T visitAura2AquilaEs(); + + T visitAura2SelenaEs(); + + T visitAura2EstrellaEs(); + + T visitAura2JavierEs(); + + T visitFluxAlexisEn(); + + T visitFluxBreeEn(); + + T visitFluxBrittanyEn(); + + T visitFluxBrookeEn(); + + T visitFluxBruceEn(); + + T visitFluxCliffEn(); + + T visitFluxColeEn(); + + T visitFluxColinEn(); + + T visitFluxConorEn(); + + T visitFluxDonovanEn(); + + T visitFluxDrewEn(); + + T visitFluxEliseEn(); + + T visitFluxGemmaEn(); + + T visitFluxHaleyEn(); + + T visitFluxHannahEn(); + + T visitFluxHeatherEn(); + + T visitFluxJackEn(); + + T visitFluxKaiEn(); + + T visitFluxKelseyEn(); + + T visitFluxKitEn(); + + T visitFluxMaeveEn(); + + T visitFluxMarceloEn(); + + T visitFluxMarcusEn(); + + T visitFluxMeenaEn(); + + T visitFluxMeghanEn(); + + T visitFluxMilesEn(); + + T visitFluxNaveenEn(); + + T visitFluxPaigeEn(); + + T visitFluxPriyaEn(); + + T visitFluxReneeEn(); + + T visitFluxRufusEn(); + + T visitFluxSeanEn(); + + T visitFluxSharonEn(); + + T visitFluxSiennaEn(); + + T visitFluxTannerEn(); + + T visitFluxWadeEn(); + + T visitFluxWesEn(); + + T visitUnknown(String unknownType); + } +} From 6c08671235072d1c5f8d24d1824bee1c65bd8795 Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:33:33 +0000 Subject: [PATCH 03/14] SDK regeneration --- .fern/metadata.json | 8 +- .../com/deepgram/AsyncDeepgramApiClient.java | 5 +- .../java/com/deepgram/DeepgramApiClient.java | 5 +- .../java/com/deepgram/core/ClientOptions.java | 84 +++++- .../deepgram/core/OkHttpWebSocketFactory.java | 22 ++ .../core/ReconnectingWebSocketListener.java | 87 +++--- .../com/deepgram/core/RetryInterceptor.java | 66 ++++- .../agent/v1/types/AgentV1AgentAudioDone.java | 7 - .../agent/v1/types/AgentV1ForceEndTurn.java | 7 - .../agent/v1/types/AgentV1KeepAlive.java | 7 - .../agent/v1/types/AgentV1ListenUpdated.java | 7 - .../agent/v1/types/AgentV1PromptUpdated.java | 7 - ...tV1SettingsAgentContextListenProvider.java | 4 +- .../AgentV1SettingsAgentListenProvider.java | 4 +- .../v1/types/AgentV1SettingsApplied.java | 7 - .../agent/v1/types/AgentV1SpeakUpdated.java | 7 - .../agent/v1/types/AgentV1ThinkUpdated.java | 7 - .../AgentV1UpdateListenListenProvider.java | 4 +- .../v1/types/AgentV1UserStartedSpeaking.java | 7 - .../agent/v1/websocket/V1WebSocketClient.java | 27 +- .../v1/websocket/V1WebSocketClient.java | 52 ++-- .../listen/v2/types/ListenV2CloseStream.java | 7 - .../v2/types/ListenV2ConfigureFailure.java | 100 ++++++- .../v2/types/ListenV2ConfigureSuccess.java | 48 +++- .../listen/v2/types/ListenV2ForceEndTurn.java | 7 - .../listen/v2/types/ListenV2Warning.java | 247 ++++++++++++++++++ .../v2/websocket/V2WebSocketClient.java | 110 ++++---- .../speak/v1/websocket/V1WebSocketClient.java | 35 +-- .../v2/audio/requests/SpeakV2Request.java | 14 +- .../speak/v2/types/SpeakV2Close.java | 7 - .../v2/types/SpeakV2ConfigureFailure.java | 6 +- .../v2/types/SpeakV2ConfigureFailureCode.java | 11 + .../speak/v2/types/SpeakV2Error.java | 6 +- .../speak/v2/types/SpeakV2Flush.java | 7 - .../speak/v2/types/SpeakV2Speak.java | 6 +- .../SpeakV2SpeechInterruptedMetadata.java | 6 +- ...echInterruptedMetadataControlsApplied.java | 18 +- .../speak/v2/types/SpeakV2SpeechMetadata.java | 6 +- .../SpeakV2SpeechMetadataControlsApplied.java | 18 +- .../speak/v2/types/SpeakV2Warning.java | 9 +- .../speak/v2/websocket/V2WebSocketClient.java | 44 ++-- .../com/deepgram/types/DeepgramModel.java | 12 - .../com/deepgram/types/SharedIntents.java | 34 +-- .../deepgram/types/SharedIntentsResults.java | 106 -------- .../types/SharedIntentsResultsIntents.java | 108 -------- ...em.java => SharedIntentsSegmentsItem.java} | 29 +- ...SharedIntentsSegmentsItemIntentsItem.java} | 19 +- .../java/com/deepgram/types/SharedTopics.java | 33 +-- .../deepgram/types/SharedTopicsResults.java | 105 -------- .../types/SharedTopicsResultsTopics.java | 107 -------- ...tem.java => SharedTopicsSegmentsItem.java} | 29 +- ...> SharedTopicsSegmentsItemTopicsItem.java} | 18 +- 52 files changed, 902 insertions(+), 841 deletions(-) create mode 100644 src/main/java/com/deepgram/resources/listen/v2/types/ListenV2Warning.java delete mode 100644 src/main/java/com/deepgram/types/SharedIntentsResults.java delete mode 100644 src/main/java/com/deepgram/types/SharedIntentsResultsIntents.java rename src/main/java/com/deepgram/types/{SharedIntentsResultsIntentsSegmentsItem.java => SharedIntentsSegmentsItem.java} (78%) rename src/main/java/com/deepgram/types/{SharedIntentsResultsIntentsSegmentsItemIntentsItem.java => SharedIntentsSegmentsItemIntentsItem.java} (81%) delete mode 100644 src/main/java/com/deepgram/types/SharedTopicsResults.java delete mode 100644 src/main/java/com/deepgram/types/SharedTopicsResultsTopics.java rename src/main/java/com/deepgram/types/{SharedTopicsResultsTopicsSegmentsItem.java => SharedTopicsSegmentsItem.java} (78%) rename src/main/java/com/deepgram/types/{SharedTopicsResultsTopicsSegmentsItemTopicsItem.java => SharedTopicsSegmentsItemTopicsItem.java} (82%) diff --git a/.fern/metadata.json b/.fern/metadata.json index b047124..87ba6bd 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "5.95.1", "generatorName": "fernapi/fern-java-sdk", - "generatorVersion": "4.21.0", + "generatorVersion": "4.23.1", "generatorConfig": { "package-prefix": "com.deepgram", "base-api-exception-class-name": "DeepgramHttpException", @@ -12,8 +12,8 @@ "enable-wire-tests": true, "runtime-version": true }, - "originGitCommit": "f6f5d3876c8996e2a6b6e59136bb222183a8e317", + "originGitCommit": "e252995bcdb25f3e12d46ae342a2b93d0c1085f9", "originGitCommitIsDirty": true, "invokedBy": "manual", - "sdkVersion": "0.10.2" -} + "sdkVersion": "0.10.3" +} \ No newline at end of file diff --git a/src/main/java/com/deepgram/AsyncDeepgramApiClient.java b/src/main/java/com/deepgram/AsyncDeepgramApiClient.java index c2aa87e..0ae6c11 100644 --- a/src/main/java/com/deepgram/AsyncDeepgramApiClient.java +++ b/src/main/java/com/deepgram/AsyncDeepgramApiClient.java @@ -79,8 +79,9 @@ public AsyncVoiceAgentClient voiceAgent() { } /** - * Releases resources owned by this client. See {@code ClientOptions.close()} for what is - * and is not released. + * Releases resources owned by this client: any WebSocket clients still connected through + * it are disconnected first, then the SDK-owned HTTP client is shut down. See + * {@code ClientOptions.close()} for what is and is not released. */ @Override public void close() { diff --git a/src/main/java/com/deepgram/DeepgramApiClient.java b/src/main/java/com/deepgram/DeepgramApiClient.java index ccb0d4a..a67528f 100644 --- a/src/main/java/com/deepgram/DeepgramApiClient.java +++ b/src/main/java/com/deepgram/DeepgramApiClient.java @@ -79,8 +79,9 @@ public VoiceAgentClient voiceAgent() { } /** - * Releases resources owned by this client. See {@code ClientOptions.close()} for what is - * and is not released. + * Releases resources owned by this client: any WebSocket clients still connected through + * it are disconnected first, then the SDK-owned HTTP client is shut down. See + * {@code ClientOptions.close()} for what is and is not released. */ @Override public void close() { diff --git a/src/main/java/com/deepgram/core/ClientOptions.java b/src/main/java/com/deepgram/core/ClientOptions.java index 5329810..e6cfae7 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -3,10 +3,14 @@ */ package com.deepgram.core; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import okhttp3.OkHttpClient; @@ -21,6 +25,10 @@ public final class ClientOptions { private final boolean ownsHttpClient; + private final AtomicBoolean closed; + + private final Set openWebSockets; + private final int timeout; private final int maxRetries; @@ -41,6 +49,8 @@ private ClientOptions( Map> headerSuppliers, OkHttpClient httpClient, boolean ownsHttpClient, + AtomicBoolean closed, + Set openWebSockets, int timeout, int maxRetries, Optional initialRetryDelayMillis, @@ -53,15 +63,17 @@ private ClientOptions( this.headers.putAll(headers); this.headers.putAll(new HashMap() { { - put("User-Agent", "com.deepgram:deepgram-java-sdk/0.10.2"); // x-release-please-version + put("User-Agent", "com.deepgram.deepgram-java-sdk/" + getSdkVersion()); put("X-Fern-Language", "JAVA"); put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); - put("X-Fern-SDK-Version", "0.10.2"); // x-release-please-version + put("X-Fern-SDK-Version", getSdkVersion()); } }); this.headerSuppliers = headerSuppliers; this.httpClient = httpClient; this.ownsHttpClient = ownsHttpClient; + this.closed = closed; + this.openWebSockets = openWebSockets; this.timeout = timeout; this.maxRetries = maxRetries; this.initialRetryDelayMillis = initialRetryDelayMillis; @@ -86,6 +98,11 @@ public Map headers(RequestOptions requestOptions) { return values; } + private static String getSdkVersion() { + String version = ClientOptions.class.getPackage().getImplementationVersion(); + return version != null ? version : "0.10.3"; + } + public int timeout(RequestOptions requestOptions) { if (requestOptions == null) { return this.timeout; @@ -132,12 +149,28 @@ public Optional retryJitterFactor() { * OkHttpClient itself; an OkHttpClient supplied via httpClient is left running, since the * caller owns its lifecycle. *

- * In-flight calls are not cancelled or awaited. Later asynchronous requests fail when the - * dispatcher rejects work, while synchronous requests may still run. Options derived from this - * one via {@code Builder.from(...)} share the same dispatcher and connection pool, so closing - * either releases them for both. Calling this method more than once has no further effect. + * In-flight calls are not cancelled or awaited, and any request issued after this method + * returns fails with a {@code RejectedExecutionException}. Options derived from this one via + * {@code Builder.from(...)} share the same dispatcher and connection pool, so closing either + * releases them for both. Calling this method more than once has no further effect. + *

+ * WebSocket clients created from this client that are still connected are disconnected + * first (whether or not the OkHttpClient is owned), so they stop reconnecting before the + * dispatcher goes away. Any WebSocket connect or reconnect attempted afterwards fails with + * an {@code IllegalStateException} explaining that the client has been closed. */ public void close() { + if (!this.closed.compareAndSet(false, true)) { + return; + } + for (AutoCloseable webSocket : new ArrayList<>(this.openWebSockets)) { + try { + webSocket.close(); + } catch (Exception e) { + // best effort; keep closing the remaining sockets and the HTTP client + } + } + this.openWebSockets.clear(); if (!this.ownsHttpClient) { return; } @@ -145,6 +178,33 @@ public void close() { this.httpClient.connectionPool().evictAll(); } + /** + * Returns whether {@link #close()} has been called on this client. + */ + public boolean isClosed() { + return this.closed.get(); + } + + /** + * Tracks a connected WebSocket client so that {@link #close()} disconnects it. + * + * @throws IllegalStateException if this client has already been closed + */ + public void registerWebSocket(AutoCloseable webSocket) { + this.openWebSockets.add(webSocket); + if (this.closed.get()) { + this.openWebSockets.remove(webSocket); + throw new IllegalStateException("root client has been closed"); + } + } + + /** + * Stops tracking a WebSocket client that has been disconnected. + */ + public void unregisterWebSocket(AutoCloseable webSocket) { + this.openWebSockets.remove(webSocket); + } + public Optional webSocketFactory() { return this.webSocketFactory; } @@ -178,6 +238,10 @@ public static class Builder { private boolean ownsHttpClient = true; + private AtomicBoolean closed; + + private Set openWebSockets; + private Optional logging = Optional.empty(); private Optional webSocketFactory = Optional.empty(); @@ -303,12 +367,18 @@ public ClientOptions build() { this.httpClient = httpClientBuilder.build(); this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + AtomicBoolean closedToUse = this.closed != null ? this.closed : new AtomicBoolean(false); + Set openWebSocketsToUse = + this.openWebSockets != null ? this.openWebSockets : ConcurrentHashMap.newKeySet(); + return new ClientOptions( environment, headers, headerSuppliers, httpClient, this.ownsHttpClient, + closedToUse, + openWebSocketsToUse, this.timeout.get(), this.maxRetries, this.initialRetryDelayMillis, @@ -327,6 +397,8 @@ public static Builder from(ClientOptions clientOptions) { builder.timeout = Optional.of(clientOptions.timeout(null)); builder.httpClient = clientOptions.httpClient(); builder.ownsHttpClient = clientOptions.ownsHttpClient; + builder.closed = clientOptions.closed; + builder.openWebSockets = clientOptions.openWebSockets; builder.headers.putAll(clientOptions.headers); builder.headerSuppliers.putAll(clientOptions.headerSuppliers); builder.maxRetries = clientOptions.maxRetries(); diff --git a/src/main/java/com/deepgram/core/OkHttpWebSocketFactory.java b/src/main/java/com/deepgram/core/OkHttpWebSocketFactory.java index 70e6c86..30653bc 100644 --- a/src/main/java/com/deepgram/core/OkHttpWebSocketFactory.java +++ b/src/main/java/com/deepgram/core/OkHttpWebSocketFactory.java @@ -3,6 +3,7 @@ */ package com.deepgram.core; +import java.util.function.BooleanSupplier; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.WebSocket; @@ -15,17 +16,38 @@ public final class OkHttpWebSocketFactory implements WebSocketFactory { private final OkHttpClient okHttpClient; + private final BooleanSupplier closedCheck; + /** * Creates a new OkHttpWebSocketFactory with the specified OkHttpClient. * * @param okHttpClient The OkHttpClient instance to use for creating WebSockets */ public OkHttpWebSocketFactory(OkHttpClient okHttpClient) { + this(okHttpClient, () -> false); + } + + /** + * Creates a new OkHttpWebSocketFactory that refuses to open WebSockets once the owning + * client has been closed. + * + * @param okHttpClient The OkHttpClient instance to use for creating WebSockets + * @param closedCheck Returns true once the owning client has been closed (e.g. + * {@code clientOptions::isClosed}) + */ + public OkHttpWebSocketFactory(OkHttpClient okHttpClient, BooleanSupplier closedCheck) { this.okHttpClient = okHttpClient; + this.closedCheck = closedCheck; } + /** + * @throws IllegalStateException if the owning client has been closed + */ @Override public WebSocket create(Request request, WebSocketListener listener) { + if (closedCheck.getAsBoolean()) { + throw new IllegalStateException("root client has been closed"); + } return okHttpClient.newWebSocket(request, listener); } } diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java index 7f6d9df..dc32da2 100644 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java @@ -14,6 +14,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Supplier; import okhttp3.Response; @@ -26,8 +27,13 @@ * Provides production-ready resilience for WebSocket connections. */ public abstract class ReconnectingWebSocketListener extends WebSocketListener { - // A single volatile reference keeps an override internally consistent. - private volatile ReconnectOptions activeOptions; + private final long minReconnectionDelayMs; + + private final long maxReconnectionDelayMs; + + private final double reconnectionDelayGrowFactor; + + private final int maxRetries; private final int maxEnqueuedMessages; @@ -49,6 +55,8 @@ public abstract class ReconnectingWebSocketListener extends WebSocketListener { private final Supplier connectionSupplier; + private final BooleanSupplier closedCheck; + /** * Creates a new reconnecting WebSocket listener. * @@ -57,52 +65,67 @@ public abstract class ReconnectingWebSocketListener extends WebSocketListener { */ public ReconnectingWebSocketListener( ReconnectingWebSocketListener.ReconnectOptions options, Supplier connectionSupplier) { - this.activeOptions = options; - this.maxEnqueuedMessages = options.maxEnqueuedMessages; - this.connectionSupplier = connectionSupplier; + this(options, connectionSupplier, () -> false); } /** - * Replaces option-derived reconnect settings without rebuilding generated clients. + * Creates a new reconnecting WebSocket listener. * - * @param options replacement options; {@code null} is a no-op + * @param options Reconnection configuration options + * @param connectionSupplier Supplier that creates new WebSocket connections + * @param closedCheck Returns true once the owning client has been closed; connect and + * reconnect attempts made after that point fail with an {@link IllegalStateException} + * instead of being retried */ - public void applyOptionsOverride(ReconnectOptions options) { - if (options != null) { - this.activeOptions = options; - } + public ReconnectingWebSocketListener( + ReconnectingWebSocketListener.ReconnectOptions options, + Supplier connectionSupplier, + BooleanSupplier closedCheck) { + this.minReconnectionDelayMs = options.minReconnectionDelayMs; + this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; + this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; + this.maxRetries = options.maxRetries; + this.maxEnqueuedMessages = options.maxEnqueuedMessages; + this.connectionSupplier = connectionSupplier; + this.closedCheck = closedCheck; } /** * Initiates a WebSocket connection with automatic reconnection enabled. * * Connection behavior: - * - Times out after {@code ReconnectOptions.connectionTimeoutMs} (default 4000ms) + * - Times out after 4000 milliseconds * - Thread-safe via atomic lock (returns immediately if connection in progress) - * - {@code maxRetries} counts retries only; {@code maxRetries(0)} means connect once, do not retry + * - Retry count not incremented for initial connection attempt * * Error handling: * - TimeoutException: Includes retry attempt context * - InterruptedException: Preserves thread interruption status * - ExecutionException: Extracts actual cause and adds context + * - Owning client closed: reports an IllegalStateException once and stops reconnecting */ public void connect() { if (!connectLock.compareAndSet(false, true)) { return; } - ReconnectOptions options = this.activeOptions; - if (retryCount.get() > options.maxRetries) { + if (closedCheck.getAsBoolean()) { + shouldReconnect.set(false); + connectLock.set(false); + onWebSocketFailure(null, new IllegalStateException("root client has been closed"), null); + return; + } + if (retryCount.get() >= maxRetries) { connectLock.set(false); return; } try { CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); try { - webSocket = connectionFuture.get(options.connectionTimeoutMs, MILLISECONDS); + webSocket = connectionFuture.get(4000, MILLISECONDS); } catch (TimeoutException e) { connectionFuture.cancel(true); TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + options.connectionTimeoutMs + " milliseconds" + new TimeoutException("WebSocket connection timeout after " + 4000 + " milliseconds" + (retryCount.get() > 0 ? " (retry attempt #" + retryCount.get() + ")" : " (initial connection attempt)")); @@ -391,20 +414,23 @@ protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, Strin * - 2+ = exponential backoff up to maxReconnectionDelayMs */ private long getNextDelay() { - ReconnectOptions options = this.activeOptions; if (retryCount.get() == 1) { - return options.minReconnectionDelayMs; + return minReconnectionDelayMs; } - long delay = (long) - (options.minReconnectionDelayMs * Math.pow(options.reconnectionDelayGrowFactor, retryCount.get() - 1)); - return Math.min(delay, options.maxReconnectionDelayMs); + long delay = (long) (minReconnectionDelayMs * Math.pow(reconnectionDelayGrowFactor, retryCount.get() - 1)); + return Math.min(delay, maxReconnectionDelayMs); } /** * Schedules a reconnection attempt with appropriate delay. * Increments retry count and uses exponential backoff. + * Does nothing once the owning client has been closed. */ private void scheduleReconnect() { + if (closedCheck.getAsBoolean()) { + shouldReconnect.set(false); + return; + } retryCount.incrementAndGet(); long delay = getNextDelay(); reconnectExecutor.schedule(this::connect, delay, MILLISECONDS); @@ -478,15 +504,12 @@ public static final class ReconnectOptions { public final int maxEnqueuedMessages; - public final long connectionTimeoutMs; - private ReconnectOptions(Builder builder) { this.minReconnectionDelayMs = builder.minReconnectionDelayMs; this.maxReconnectionDelayMs = builder.maxReconnectionDelayMs; this.reconnectionDelayGrowFactor = builder.reconnectionDelayGrowFactor; this.maxRetries = builder.maxRetries; this.maxEnqueuedMessages = builder.maxEnqueuedMessages; - this.connectionTimeoutMs = builder.connectionTimeoutMs; } public static Builder builder() { @@ -504,15 +527,12 @@ public static final class Builder { private int maxEnqueuedMessages; - private long connectionTimeoutMs; - public Builder() { this.minReconnectionDelayMs = 1000; this.maxReconnectionDelayMs = 10000; this.reconnectionDelayGrowFactor = 1.3; this.maxRetries = 2147483647; this.maxEnqueuedMessages = 1000; - this.connectionTimeoutMs = 4000; } public Builder minReconnectionDelayMs(long minReconnectionDelayMs) { @@ -540,14 +560,6 @@ public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { return this; } - /** - * Sets the per-attempt connection timeout in milliseconds. Defaults to {@code 4000}. - */ - public Builder connectionTimeoutMs(long connectionTimeoutMs) { - this.connectionTimeoutMs = connectionTimeoutMs; - return this; - } - /** * Builds the ReconnectOptions with validation. * @@ -580,9 +592,6 @@ public ReconnectOptions build() { if (maxEnqueuedMessages < 0) { throw new IllegalArgumentException("maxEnqueuedMessages must be non-negative"); } - if (connectionTimeoutMs <= 0) { - throw new IllegalArgumentException("connectionTimeoutMs must be positive"); - } return new ReconnectOptions(this); } } diff --git a/src/main/java/com/deepgram/core/RetryInterceptor.java b/src/main/java/com/deepgram/core/RetryInterceptor.java index a0959bb..aad032c 100644 --- a/src/main/java/com/deepgram/core/RetryInterceptor.java +++ b/src/main/java/com/deepgram/core/RetryInterceptor.java @@ -10,9 +10,13 @@ import java.time.format.DateTimeParseException; import java.util.Optional; import java.util.Random; +import okhttp3.Call; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.AsyncTimeout; +import okio.Timeout; public class RetryInterceptor implements Interceptor { @@ -78,13 +82,28 @@ private Response retryChain(Response response, Chain chain, int maxRetries) thro ExponentialBackoff backoff = new ExponentialBackoff(maxRetries); Optional nextBackoff = backoff.nextBackoff(response); while (nextBackoff.isPresent()) { + response = bufferResponse(response); + Optional callTimeout = suspendCallTimeout(chain); try { Thread.sleep(nextBackoff.get().toMillis()); } catch (InterruptedException e) { + response.close(); throw new IOException("Interrupted while trying request", e); + } finally { + callTimeout.ifPresent(AsyncTimeout::enter); + } + Response nextResponse; + try { + nextResponse = chain.proceed(chain.request()); + } catch (IOException e) { + // A retry attempt failed with a transport error (connection reset, unreachable host, ...). The + // error is intentionally dropped: the response already received from the API is more actionable. + // Note that a call-timeout expiry or an explicit cancel() also ends up here, but OkHttp then + // discards whatever the interceptor chain returns and reports the cancellation itself. + return response; } response.close(); - response = chain.proceed(chain.request()); + response = nextResponse; if (shouldRetry(response.code())) { nextBackoff = backoff.nextBackoff(response); } else { @@ -95,6 +114,51 @@ private Response retryChain(Response response, Chain chain, int maxRetries) thro return response; } + /** + * Pauses the OkHttp call timeout so that it applies to each attempt individually rather than to the whole + * retry loop: the backoff wait (e.g. a {@code Retry-After} the size of the call timeout) does not consume the + * budget of the next attempt, and each attempt starts with a fresh budget once {@link AsyncTimeout#enter()} + * is called again. + * + *

{@link AsyncTimeout#exit()} and {@link AsyncTimeout#enter()} must stay strictly paired: {@code exit()} on + * a timeout that is not armed is a no-op, but {@code enter()} on one that is already armed throws + * {@code IllegalStateException}. Every caller must re-enter the returned timeout exactly once, on all paths. + */ + private static Optional suspendCallTimeout(Chain chain) { + Call call = chain.call(); + if (call == null) { + return Optional.empty(); + } + Timeout timeout = call.timeout(); + if (!(timeout instanceof AsyncTimeout)) { + return Optional.empty(); + } + AsyncTimeout callTimeout = (AsyncTimeout) timeout; + callTimeout.exit(); + return Optional.of(callTimeout); + } + + /** + * Reads the body into memory so the response stays usable after the underlying connection + * is released or cancelled by a later attempt. + */ + private static Response bufferResponse(Response response) { + ResponseBody body = response.body(); + if (body == null) { + return response; + } + try { + byte[] bytes = body.bytes(); + Response buffered = response.newBuilder() + .body(ResponseBody.create(bytes, body.contentType())) + .build(); + response.close(); + return buffered; + } catch (IOException e) { + return response; + } + } + /** * Calculates the retry delay from response headers, with fallback to exponential backoff. * Priority: Retry-After > X-RateLimit-Reset > Exponential Backoff diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java index b6ee900..71825ed 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1AgentAudioDone; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java index 11d0d8d..1672d2d 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1ForceEndTurn; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java index 588aad1..7842266 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1KeepAlive; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java index 3de75ec..65878c9 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1ListenUpdated; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java index 2b2f794..68d71cc 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1PromptUpdated; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java index 1c21ce7..eea1585 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.types.DeepgramListenProviderV1; import com.deepgram.types.DeepgramListenProviderV2; import com.fasterxml.jackson.annotation.JsonCreator; @@ -101,7 +99,7 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java index 06e47e6..4309ec0 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.types.DeepgramListenProviderV1; import com.deepgram.types.DeepgramListenProviderV2; import com.fasterxml.jackson.annotation.JsonCreator; @@ -101,7 +99,7 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java index 443c214..0d60b72 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1SettingsApplied; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java index 87c8d7d..747aa9d 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1SpeakUpdated; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java index 4b6f623..5668a71 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1ThinkUpdated; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java index d0390e2..e693a04 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.types.DeepgramListenProviderV1; import com.deepgram.types.DeepgramListenProviderV2; import com.fasterxml.jackson.annotation.JsonCreator; @@ -101,7 +99,7 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java index 36e8a9d..edb80f8 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1UserStartedSpeaking; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java index d8305b1..5bf4d73 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; @@ -162,13 +160,22 @@ public CompletableFuture connect() { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { readyState = WebSocketReadyState.OPEN; @@ -207,6 +214,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { } } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -215,6 +223,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } diff --git a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java index ffaa903..88c9ebe 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java @@ -3,12 +3,9 @@ */ package com.deepgram.resources.listen.v1.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -141,7 +138,8 @@ public CompletableFuture connect(V1ConnectOptions options) { "endpointing", String.valueOf(options.getEndpointing().get())); } if (options.getExtra() != null && options.getExtra().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "extra", options.getExtra().get().get(), true); + urlBuilder.addQueryParameter( + "extra", String.valueOf(options.getExtra().get())); } if (options.getInterimResults() != null && options.getInterimResults().isPresent()) { urlBuilder.addQueryParameter( @@ -149,10 +147,12 @@ public CompletableFuture connect(V1ConnectOptions options) { String.valueOf(options.getInterimResults().get())); } if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "keyterm", options.getKeyterm().get().get(), true); + urlBuilder.addQueryParameter( + "keyterm", String.valueOf(options.getKeyterm().get())); } if (options.getKeywords() != null && options.getKeywords().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "keywords", options.getKeywords().get().get(), true); + urlBuilder.addQueryParameter( + "keywords", String.valueOf(options.getKeywords().get())); } if (options.getLanguage() != null && options.getLanguage().isPresent()) { urlBuilder.addQueryParameter( @@ -185,21 +185,23 @@ public CompletableFuture connect(V1ConnectOptions options) { "redact", String.valueOf(options.getRedact().get())); } if (options.getReplace() != null && options.getReplace().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "replace", options.getReplace().get().get(), true); + urlBuilder.addQueryParameter( + "replace", String.valueOf(options.getReplace().get())); } if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { urlBuilder.addQueryParameter( "sample_rate", String.valueOf(options.getSampleRate().get())); } if (options.getSearch() != null && options.getSearch().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "search", options.getSearch().get().get(), true); + urlBuilder.addQueryParameter( + "search", String.valueOf(options.getSearch().get())); } if (options.getSmartFormat() != null && options.getSmartFormat().isPresent()) { urlBuilder.addQueryParameter( "smart_format", String.valueOf(options.getSmartFormat().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); + urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); } if (options.getUtteranceEndMs() != null && options.getUtteranceEndMs().isPresent()) { urlBuilder.addQueryParameter( @@ -214,13 +216,6 @@ public CompletableFuture connect(V1ConnectOptions options) { urlBuilder.addQueryParameter( "version", String.valueOf(options.getVersion().get())); } - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); @@ -229,13 +224,22 @@ public CompletableFuture connect(V1ConnectOptions options) { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { readyState = WebSocketReadyState.OPEN; @@ -270,6 +274,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { } } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -278,6 +283,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java index 8f7496d..e6308e2 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.listen.v2.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof ListenV2CloseStream; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureFailure.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureFailure.java index 478307b..a48faac 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureFailure.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureFailure.java @@ -10,10 +10,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @@ -23,11 +25,22 @@ public final class ListenV2ConfigureFailure { private final int sequenceId; + private final Optional code; + + private final Optional description; + private final Map additionalProperties; - private ListenV2ConfigureFailure(String requestId, int sequenceId, Map additionalProperties) { + private ListenV2ConfigureFailure( + String requestId, + int sequenceId, + Optional code, + Optional description, + Map additionalProperties) { this.requestId = requestId; this.sequenceId = sequenceId; + this.code = code; + this.description = description; this.additionalProperties = additionalProperties; } @@ -57,6 +70,22 @@ public int getSequenceId() { return sequenceId; } + /** + * @return Failure code identifying the rejected configuration + */ + @JsonProperty("code") + public Optional getCode() { + return code; + } + + /** + * @return A human-readable description of the configuration failure + */ + @JsonProperty("description") + public Optional getDescription() { + return description; + } + @java.lang.Override public boolean equals(Object other) { if (this == other) return true; @@ -69,12 +98,15 @@ public Map getAdditionalProperties() { } private boolean equalTo(ListenV2ConfigureFailure other) { - return requestId.equals(other.requestId) && sequenceId == other.sequenceId; + return requestId.equals(other.requestId) + && sequenceId == other.sequenceId + && code.equals(other.code) + && description.equals(other.description); } @java.lang.Override public int hashCode() { - return Objects.hash(this.requestId, this.sequenceId); + return Objects.hash(this.requestId, this.sequenceId, this.code, this.description); } @java.lang.Override @@ -110,6 +142,20 @@ public interface _FinalStage { _FinalStage additionalProperty(String key, Object value); _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Failure code identifying the rejected configuration

+ */ + _FinalStage code(Optional code); + + _FinalStage code(String code); + + /** + *

A human-readable description of the configuration failure

+ */ + _FinalStage description(Optional description); + + _FinalStage description(String description); } @JsonIgnoreProperties(ignoreUnknown = true) @@ -118,6 +164,10 @@ public static final class Builder implements RequestIdStage, SequenceIdStage, _F private int sequenceId; + private Optional description = Optional.empty(); + + private Optional code = Optional.empty(); + @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -127,6 +177,8 @@ private Builder() {} public Builder from(ListenV2ConfigureFailure other) { requestId(other.getRequestId()); sequenceId(other.getSequenceId()); + code(other.getCode()); + description(other.getDescription()); return this; } @@ -154,9 +206,49 @@ public _FinalStage sequenceId(int sequenceId) { return this; } + /** + *

A human-readable description of the configuration failure

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage description(String description) { + this.description = Optional.ofNullable(description); + return this; + } + + /** + *

A human-readable description of the configuration failure

+ */ + @java.lang.Override + @JsonSetter(value = "description", nulls = Nulls.SKIP) + public _FinalStage description(Optional description) { + this.description = description; + return this; + } + + /** + *

Failure code identifying the rejected configuration

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage code(String code) { + this.code = Optional.ofNullable(code); + return this; + } + + /** + *

Failure code identifying the rejected configuration

+ */ + @java.lang.Override + @JsonSetter(value = "code", nulls = Nulls.SKIP) + public _FinalStage code(Optional code) { + this.code = code; + return this; + } + @java.lang.Override public ListenV2ConfigureFailure build() { - return new ListenV2ConfigureFailure(requestId, sequenceId, additionalProperties); + return new ListenV2ConfigureFailure(requestId, sequenceId, code, description, additionalProperties); } @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureSuccess.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureSuccess.java index ca93f2b..a714b95 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureSuccess.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureSuccess.java @@ -31,6 +31,8 @@ public final class ListenV2ConfigureSuccess { private final Optional> languageHints; + private final Optional numerals; + private final int sequenceId; private final Map additionalProperties; @@ -40,12 +42,14 @@ private ListenV2ConfigureSuccess( ListenV2ConfigureSuccessThresholds thresholds, ListenV2Keyterm keyterms, Optional> languageHints, + Optional numerals, int sequenceId, Map additionalProperties) { this.requestId = requestId; this.thresholds = thresholds; this.keyterms = keyterms; this.languageHints = languageHints; + this.numerals = numerals; this.sequenceId = sequenceId; this.additionalProperties = additionalProperties; } @@ -88,6 +92,14 @@ public Optional> getLanguageHints() { return languageHints; } + /** + * @return Whether numeral formatting is enabled for transcripts Flux STT sends after it processes the update. + */ + @JsonProperty("numerals") + public Optional getNumerals() { + return numerals; + } + /** * @return Starts at 0 and increments for each message the server sends * to the client. This includes messages of other types, like @@ -114,12 +126,14 @@ private boolean equalTo(ListenV2ConfigureSuccess other) { && thresholds.equals(other.thresholds) && keyterms.equals(other.keyterms) && languageHints.equals(other.languageHints) + && numerals.equals(other.numerals) && sequenceId == other.sequenceId; } @java.lang.Override public int hashCode() { - return Objects.hash(this.requestId, this.thresholds, this.keyterms, this.languageHints, this.sequenceId); + return Objects.hash( + this.requestId, this.thresholds, this.keyterms, this.languageHints, this.numerals, this.sequenceId); } @java.lang.Override @@ -174,6 +188,13 @@ public interface _FinalStage { _FinalStage languageHints(Optional> languageHints); _FinalStage languageHints(List languageHints); + + /** + *

Whether numeral formatting is enabled for transcripts Flux STT sends after it processes the update.

+ */ + _FinalStage numerals(Optional numerals); + + _FinalStage numerals(Boolean numerals); } @JsonIgnoreProperties(ignoreUnknown = true) @@ -187,6 +208,8 @@ public static final class Builder private int sequenceId; + private Optional numerals = Optional.empty(); + private Optional> languageHints = Optional.empty(); @JsonAnySetter @@ -200,6 +223,7 @@ public Builder from(ListenV2ConfigureSuccess other) { thresholds(other.getThresholds()); keyterms(other.getKeyterms()); languageHints(other.getLanguageHints()); + numerals(other.getNumerals()); sequenceId(other.getSequenceId()); return this; } @@ -247,6 +271,26 @@ public _FinalStage sequenceId(int sequenceId) { return this; } + /** + *

Whether numeral formatting is enabled for transcripts Flux STT sends after it processes the update.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage numerals(Boolean numerals) { + this.numerals = Optional.ofNullable(numerals); + return this; + } + + /** + *

Whether numeral formatting is enabled for transcripts Flux STT sends after it processes the update.

+ */ + @java.lang.Override + @JsonSetter(value = "numerals", nulls = Nulls.SKIP) + public _FinalStage numerals(Optional numerals) { + this.numerals = numerals; + return this; + } + /** *

The currently active language hints. Only applicable to the flux-general-multi model.

* @return Reference to {@code this} so that method calls can be chained together. @@ -270,7 +314,7 @@ public _FinalStage languageHints(Optional> languageHints) { @java.lang.Override public ListenV2ConfigureSuccess build() { return new ListenV2ConfigureSuccess( - requestId, thresholds, keyterms, languageHints, sequenceId, additionalProperties); + requestId, thresholds, keyterms, languageHints, numerals, sequenceId, additionalProperties); } @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java index 99471a5..c3dbd38 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.listen.v2.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof ListenV2ForceEndTurn; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2Warning.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2Warning.java new file mode 100644 index 0000000..4eed2fb --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2Warning.java @@ -0,0 +1,247 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v2.types; + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ListenV2Warning.Builder.class) +public final class ListenV2Warning { + private final String requestId; + + private final int sequenceId; + + private final String code; + + private final String description; + + private final Map additionalProperties; + + private ListenV2Warning( + String requestId, + int sequenceId, + String code, + String description, + Map additionalProperties) { + this.requestId = requestId; + this.sequenceId = sequenceId; + this.code = code; + this.description = description; + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "Warning"; + } + + /** + * @return The unique identifier of the request + */ + @JsonProperty("request_id") + public String getRequestId() { + return requestId; + } + + /** + * @return Starts at 0 and increments for each message the server sends + * to the client. This includes messages of other types, like + * TurnInfo messages. + */ + @JsonProperty("sequence_id") + public int getSequenceId() { + return sequenceId; + } + + /** + * @return Warning code identifying the condition, in SCREAMING_SNAKE_CASE + */ + @JsonProperty("code") + public String getCode() { + return code; + } + + /** + * @return A human-readable description of the warning + */ + @JsonProperty("description") + public String getDescription() { + return description; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ListenV2Warning && equalTo((ListenV2Warning) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ListenV2Warning other) { + return requestId.equals(other.requestId) + && sequenceId == other.sequenceId + && code.equals(other.code) + && description.equals(other.description); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.requestId, this.sequenceId, this.code, this.description); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static RequestIdStage builder() { + return new Builder(); + } + + public interface RequestIdStage { + /** + *

The unique identifier of the request

+ */ + SequenceIdStage requestId(@NotNull String requestId); + + Builder from(ListenV2Warning other); + } + + public interface SequenceIdStage { + /** + *

Starts at 0 and increments for each message the server sends + * to the client. This includes messages of other types, like + * TurnInfo messages.

+ */ + CodeStage sequenceId(int sequenceId); + } + + public interface CodeStage { + /** + *

Warning code identifying the condition, in SCREAMING_SNAKE_CASE

+ */ + DescriptionStage code(@NotNull String code); + } + + public interface DescriptionStage { + /** + *

A human-readable description of the warning

+ */ + _FinalStage description(@NotNull String description); + } + + public interface _FinalStage { + ListenV2Warning build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder + implements RequestIdStage, SequenceIdStage, CodeStage, DescriptionStage, _FinalStage { + private String requestId; + + private int sequenceId; + + private String code; + + private String description; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(ListenV2Warning other) { + requestId(other.getRequestId()); + sequenceId(other.getSequenceId()); + code(other.getCode()); + description(other.getDescription()); + return this; + } + + /** + *

The unique identifier of the request

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("request_id") + public SequenceIdStage requestId(@NotNull String requestId) { + this.requestId = Objects.requireNonNull(requestId, "requestId must not be null"); + return this; + } + + /** + *

Starts at 0 and increments for each message the server sends + * to the client. This includes messages of other types, like + * TurnInfo messages.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("sequence_id") + public CodeStage sequenceId(int sequenceId) { + this.sequenceId = sequenceId; + return this; + } + + /** + *

Warning code identifying the condition, in SCREAMING_SNAKE_CASE

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("code") + public DescriptionStage code(@NotNull String code) { + this.code = Objects.requireNonNull(code, "code must not be null"); + return this; + } + + /** + *

A human-readable description of the warning

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("description") + public _FinalStage description(@NotNull String description) { + this.description = Objects.requireNonNull(description, "description must not be null"); + return this; + } + + @java.lang.Override + public ListenV2Warning build() { + return new ListenV2Warning(requestId, sequenceId, code, description, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java index b2dee9b..84bf5df 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java @@ -3,12 +3,9 @@ */ package com.deepgram.resources.listen.v2.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -20,11 +17,11 @@ import com.deepgram.resources.listen.v2.types.ListenV2FatalError; import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; import com.deepgram.resources.listen.v2.types.ListenV2TurnInfo; +import com.deepgram.resources.listen.v2.types.ListenV2Warning; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -62,8 +59,6 @@ public class V2WebSocketClient implements AutoCloseable { private ReconnectingWebSocketListener reconnectingListener; - private final AtomicReference closeStreamSocket = new AtomicReference<>(); - private volatile Consumer connectedHandler; private volatile Consumer turnInfoHandler; @@ -72,6 +67,8 @@ public class V2WebSocketClient implements AutoCloseable { private volatile Consumer configureFailureHandler; + private volatile Consumer warningHandler; + private volatile Consumer errorHandler; /** @@ -90,7 +87,6 @@ public V2WebSocketClient(ClientOptions clientOptions) { */ public CompletableFuture connect(V2ConnectOptions options) { connectionFuture = new CompletableFuture<>(); - closeStreamSocket.set(null); String baseUrl = clientOptions.environment().getProductionURL(); String fullPath = "/v2/listen"; if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { @@ -133,12 +129,12 @@ public CompletableFuture connect(V2ConnectOptions options) { "eot_timeout_ms", String.valueOf(options.getEotTimeoutMs().get())); } if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "keyterm", options.getKeyterm().get().get(), true); + urlBuilder.addQueryParameter( + "keyterm", String.valueOf(options.getKeyterm().get())); } if (options.getLanguageHint() != null && options.getLanguageHint().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "language_hint", options.getLanguageHint().get().get(), true); + urlBuilder.addQueryParameter( + "language_hint", String.valueOf(options.getLanguageHint().get())); } if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { urlBuilder.addQueryParameter( @@ -158,14 +154,7 @@ public CompletableFuture connect(V2ConnectOptions options) { "mip_opt_out", String.valueOf(options.getMipOptOut().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); - } - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); + urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); @@ -175,16 +164,24 @@ public CompletableFuture connect(V2ConnectOptions options) { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { - closeStreamSocket.set(null); readyState = WebSocketReadyState.OPEN; if (onConnectedHandler != null) { onConnectedHandler.run(); @@ -216,13 +213,8 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { onDisconnectedHandler.accept(new DisconnectReason(code, reason)); } } - - @Override - protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, String reason) { - return super.shouldReconnectAfterClose(webSocket, code, reason) - && (code != 1005 || closeStreamSocket.get() != webSocket); - } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -231,6 +223,7 @@ protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, Strin * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } @@ -270,22 +263,16 @@ public CompletableFuture sendMedia(ByteString message) { } /** - * Sends a ListenV2CloseStream message to the server asynchronously. A queued CloseStream does - * not suppress reconnects because it might never reach the server. + * Sends a ListenV2CloseStream message to the server asynchronously. * @param message the message to send * @return a CompletableFuture that completes when the message is sent */ public CompletableFuture sendCloseStream(ListenV2CloseStream message) { - return sendMessage(message, closeStreamSocket::set); + return sendMessage(message); } /** * Sends a ListenV2ForceEndTurn message to the server asynchronously. - * - *

Sending this with no turn in progress is not an error. The server answers with a - * {@code Warning} frame carrying code {@code FORCE_END_TURN_NO_ACTIVE_TURN} and ignores the - * request. Listen V2 has no typed warning event yet, so that frame is delivered as raw JSON - * to {@link #onMessage(java.util.function.Consumer)}. * @param message the message to send * @return a CompletableFuture that completes when the message is sent */ @@ -334,6 +321,14 @@ public void onConfigureFailure(Consumer handler) { this.configureFailureHandler = handler; } + /** + * Registers a handler for ListenV2Warning messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onWarning(Consumer handler) { + this.warningHandler = handler; + } + /** * Registers a handler for ListenV2FatalError messages from the server. * @param handler the handler to invoke when a message is received @@ -351,8 +346,7 @@ public void onConnected(Runnable handler) { } /** - * Registers a handler called when the connection is closed. An empty peer close frame is - * reported with code 1005, even though the SDK acknowledges it with code 1000. + * Registers a handler called when the connection is closed. * @param handler the handler to invoke when disconnected */ public void onDisconnected(Consumer handler) { @@ -408,16 +402,12 @@ private void assertSocketIsOpen() { } private CompletableFuture sendMessage(Object body) { - return sendMessage(body, null); - } - - private CompletableFuture sendMessage(Object body, Consumer onSent) { CompletableFuture future = new CompletableFuture<>(); try { assertSocketIsOpen(); String json = objectMapper.writeValueAsString(body); // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json, onSent); + reconnectingListener.send(json); future.complete(null); } catch (IllegalStateException e) { future.completeExceptionally(e); @@ -475,6 +465,23 @@ private void handleIncomingMessage(String json) { return; } } + if (node.has("request_id") + && node.has("sequence_id") + && node.has("code") + && node.has("description") + && "Warning".equals(node.path("type").asText())) { + ListenV2Warning warningHandlerEvent = null; + try { + warningHandlerEvent = objectMapper.treeToValue(node, ListenV2Warning.class); + } catch (Exception e) { + } + if (warningHandlerEvent != null) { + if (warningHandler != null) { + warningHandler.accept(warningHandlerEvent); + } + return; + } + } if (node.has("sequence_id") && node.has("code") && node.has("description") @@ -521,8 +528,11 @@ private void handleIncomingMessage(String json) { return; } } - // The raw frame is already delivered to onMessage(String), so ignore unknown typed - // frames for forward compatibility with newly added server control messages. + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException( + "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) + + "... Update your SDK version to support new message types.")); + } } catch (Exception e) { if (onErrorHandler != null) { onErrorHandler.accept(e); diff --git a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java index ae271f2..6dd298a 100644 --- a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java @@ -3,12 +3,9 @@ */ package com.deepgram.resources.speak.v1.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -125,13 +122,6 @@ public CompletableFuture connect(V1ConnectOptions options) { urlBuilder.addQueryParameter( "speed", String.valueOf(options.getSpeed().get())); } - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); @@ -140,13 +130,22 @@ public CompletableFuture connect(V1ConnectOptions options) { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { readyState = WebSocketReadyState.OPEN; @@ -185,6 +184,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { } } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -201,6 +201,7 @@ public CompletableFuture connect() { * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } diff --git a/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java b/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java index bc3fb63..d1a3d82 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java +++ b/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java @@ -167,7 +167,7 @@ public Optional getSampleRate() { } /** - * @return Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages. + * @return Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages. When the text contains an inline pause marker, speed is capped at 1.15 (PAUSE_SPEED_CAP_EXCEEDED above that). A value other than 1.0 cannot be combined with inline pronunciation controls (CONTROL_COMBINATION_INVALID). */ @JsonIgnore public Optional getSpeed() { @@ -183,7 +183,7 @@ public Optional getPriority() { } /** - * @return The text content to be converted to speech. The server normalizes and preprocesses the text before synthesis. Inline pause and pronunciation controls are not yet applied; they are stripped from the text before synthesis. + * @return The text content to be converted to speech. The server normalizes and preprocesses the text before synthesis. May contain inline pause controls (\{pause:500ms\}, 500-3000 ms in 100 ms steps, at most 8 per request) and inline pronunciation controls (\{"word": "...", "pronounce": "<IPA>"\}, Early Access). Pronunciation cannot be combined with pause or with a speed other than 1.0, and speed is capped at 1.15 when a pause is present. See Speed, Pause, Pronunciation. */ @JsonProperty("text") public String getText() { @@ -255,7 +255,7 @@ public interface ModelStage { public interface TextStage { /** - *

The text content to be converted to speech. The server normalizes and preprocesses the text before synthesis. Inline pause and pronunciation controls are not yet applied; they are stripped from the text before synthesis.

+ *

The text content to be converted to speech. The server normalizes and preprocesses the text before synthesis. May contain inline pause controls (\{pause:500ms\}, 500-3000 ms in 100 ms steps, at most 8 per request) and inline pronunciation controls (\{"word": "...", "pronounce": "<IPA>"\}, Early Access). Pronunciation cannot be combined with pause or with a speed other than 1.0, and speed is capped at 1.15 when a pause is present. See Speed, Pause, Pronunciation.

*/ _FinalStage text(@NotNull String text); } @@ -333,7 +333,7 @@ public interface _FinalStage { _FinalStage sampleRate(Integer sampleRate); /** - *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages.

+ *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages. When the text contains an inline pause marker, speed is capped at 1.15 (PAUSE_SPEED_CAP_EXCEEDED above that). A value other than 1.0 cannot be combined with inline pronunciation controls (CONTROL_COMBINATION_INVALID).

*/ _FinalStage speed(Optional speed); @@ -410,7 +410,7 @@ public TextStage model(@NotNull String model) { } /** - *

The text content to be converted to speech. The server normalizes and preprocesses the text before synthesis. Inline pause and pronunciation controls are not yet applied; they are stripped from the text before synthesis.

+ *

The text content to be converted to speech. The server normalizes and preprocesses the text before synthesis. May contain inline pause controls (\{pause:500ms\}, 500-3000 ms in 100 ms steps, at most 8 per request) and inline pronunciation controls (\{"word": "...", "pronounce": "<IPA>"\}, Early Access). Pronunciation cannot be combined with pause or with a speed other than 1.0, and speed is capped at 1.15 when a pause is present. See Speed, Pause, Pronunciation.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override @@ -441,7 +441,7 @@ public _FinalStage priority(Optional priority) { } /** - *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages.

+ *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages. When the text contains an inline pause marker, speed is capped at 1.15 (PAUSE_SPEED_CAP_EXCEEDED above that). A value other than 1.0 cannot be combined with inline pronunciation controls (CONTROL_COMBINATION_INVALID).

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override @@ -451,7 +451,7 @@ public _FinalStage speed(Double speed) { } /** - *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages.

+ *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages. When the text contains an inline pause marker, speed is capped at 1.15 (PAUSE_SPEED_CAP_EXCEEDED above that). A value other than 1.0 cannot be combined with inline pronunciation controls (CONTROL_COMBINATION_INVALID).

*/ @java.lang.Override @JsonSetter(value = "speed", nulls = Nulls.SKIP) diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java index 0479ff6..2e96a5c 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.speak.v2.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof SpeakV2Close; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2ConfigureFailure.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2ConfigureFailure.java index e43c252..7970232 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2ConfigureFailure.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2ConfigureFailure.java @@ -53,7 +53,7 @@ public String getType() { } /** - * @return Failure code, in SCREAMING_SNAKE_CASE. SPEED_OUT_OF_RANGE: outside the range the model publishes. SPEED_INCREMENT_INVALID: inside the published range but off the 0.05 increment. SPEED_NOT_SUPPORTED: this model or language has no runtime speed control at all. INTERNAL_ERROR: the configuration was acceptable but the server could not apply it — unlike the others, a server-side failure rather than a statement about the request. + * @return Failure code, in SCREAMING_SNAKE_CASE. SPEED_OUT_OF_RANGE: outside the range the model publishes. SPEED_INCREMENT_INVALID: inside the published range but off the 0.05 increment. SPEED_NOT_SUPPORTED: this model or language has no runtime speed control at all. CONTROL_COMBINATION_INVALID: speed was set while a turn buffered behind the active one still carries a pronunciation control; pronunciation and speed cannot be combined, so flush that turn before setting speed (pronunciations in the active turn do not block the change). INTERNAL_ERROR: the configuration was acceptable but the server could not apply it — unlike the others, a server-side failure rather than a statement about the request. */ @JsonProperty("code") public SpeakV2ConfigureFailureCode getCode() { @@ -118,7 +118,7 @@ public static CodeStage builder() { public interface CodeStage { /** - *

Failure code, in SCREAMING_SNAKE_CASE. SPEED_OUT_OF_RANGE: outside the range the model publishes. SPEED_INCREMENT_INVALID: inside the published range but off the 0.05 increment. SPEED_NOT_SUPPORTED: this model or language has no runtime speed control at all. INTERNAL_ERROR: the configuration was acceptable but the server could not apply it — unlike the others, a server-side failure rather than a statement about the request.

+ *

Failure code, in SCREAMING_SNAKE_CASE. SPEED_OUT_OF_RANGE: outside the range the model publishes. SPEED_INCREMENT_INVALID: inside the published range but off the 0.05 increment. SPEED_NOT_SUPPORTED: this model or language has no runtime speed control at all. CONTROL_COMBINATION_INVALID: speed was set while a turn buffered behind the active one still carries a pronunciation control; pronunciation and speed cannot be combined, so flush that turn before setting speed (pronunciations in the active turn do not block the change). INTERNAL_ERROR: the configuration was acceptable but the server could not apply it — unlike the others, a server-side failure rather than a statement about the request.

*/ DescriptionStage code(@NotNull SpeakV2ConfigureFailureCode code); @@ -179,7 +179,7 @@ public Builder from(SpeakV2ConfigureFailure other) { } /** - *

Failure code, in SCREAMING_SNAKE_CASE. SPEED_OUT_OF_RANGE: outside the range the model publishes. SPEED_INCREMENT_INVALID: inside the published range but off the 0.05 increment. SPEED_NOT_SUPPORTED: this model or language has no runtime speed control at all. INTERNAL_ERROR: the configuration was acceptable but the server could not apply it — unlike the others, a server-side failure rather than a statement about the request.

+ *

Failure code, in SCREAMING_SNAKE_CASE. SPEED_OUT_OF_RANGE: outside the range the model publishes. SPEED_INCREMENT_INVALID: inside the published range but off the 0.05 increment. SPEED_NOT_SUPPORTED: this model or language has no runtime speed control at all. CONTROL_COMBINATION_INVALID: speed was set while a turn buffered behind the active one still carries a pronunciation control; pronunciation and speed cannot be combined, so flush that turn before setting speed (pronunciations in the active turn do not block the change). INTERNAL_ERROR: the configuration was acceptable but the server could not apply it — unlike the others, a server-side failure rather than a statement about the request.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2ConfigureFailureCode.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2ConfigureFailureCode.java index 7d02bce..63ec45a 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2ConfigureFailureCode.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2ConfigureFailureCode.java @@ -7,6 +7,9 @@ import com.fasterxml.jackson.annotation.JsonValue; public final class SpeakV2ConfigureFailureCode { + public static final SpeakV2ConfigureFailureCode CONTROL_COMBINATION_INVALID = + new SpeakV2ConfigureFailureCode(Value.CONTROL_COMBINATION_INVALID, "CONTROL_COMBINATION_INVALID"); + public static final SpeakV2ConfigureFailureCode SPEED_OUT_OF_RANGE = new SpeakV2ConfigureFailureCode(Value.SPEED_OUT_OF_RANGE, "SPEED_OUT_OF_RANGE"); @@ -52,6 +55,8 @@ public int hashCode() { public T visit(Visitor visitor) { switch (value) { + case CONTROL_COMBINATION_INVALID: + return visitor.visitControlCombinationInvalid(); case SPEED_OUT_OF_RANGE: return visitor.visitSpeedOutOfRange(); case SPEED_INCREMENT_INVALID: @@ -69,6 +74,8 @@ public T visit(Visitor visitor) { @JsonCreator(mode = JsonCreator.Mode.DELEGATING) public static SpeakV2ConfigureFailureCode valueOf(String value) { switch (value) { + case "CONTROL_COMBINATION_INVALID": + return CONTROL_COMBINATION_INVALID; case "SPEED_OUT_OF_RANGE": return SPEED_OUT_OF_RANGE; case "SPEED_INCREMENT_INVALID": @@ -89,6 +96,8 @@ public enum Value { SPEED_NOT_SUPPORTED, + CONTROL_COMBINATION_INVALID, + INTERNAL_ERROR, UNKNOWN @@ -101,6 +110,8 @@ public interface Visitor { T visitSpeedNotSupported(); + T visitControlCombinationInvalid(); + T visitInternalError(); T visitUnknown(String unknownType); diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Error.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Error.java index 343cfa7..0a82847 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Error.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Error.java @@ -40,7 +40,7 @@ public String getType() { } /** - * @return A code identifying the error, e.g. MESSAGE-0000 or NET-0000. + * @return A code identifying the error, e.g. MESSAGE-0000 or NET-0000. DATA-0002 covers invalid inline controls and speed, including an inline pause marker (pause is batch-only) and a pronunciation control combined with a speed other than 1.0; description names the specific rule. */ @JsonProperty("code") public SpeakV2ErrorCode getCode() { @@ -86,7 +86,7 @@ public static CodeStage builder() { public interface CodeStage { /** - *

A code identifying the error, e.g. MESSAGE-0000 or NET-0000.

+ *

A code identifying the error, e.g. MESSAGE-0000 or NET-0000. DATA-0002 covers invalid inline controls and speed, including an inline pause marker (pause is batch-only) and a pronunciation control combined with a speed other than 1.0; description names the specific rule.

*/ DescriptionStage code(@NotNull SpeakV2ErrorCode code); @@ -127,7 +127,7 @@ public Builder from(SpeakV2Error other) { } /** - *

A code identifying the error, e.g. MESSAGE-0000 or NET-0000.

+ *

A code identifying the error, e.g. MESSAGE-0000 or NET-0000. DATA-0002 covers invalid inline controls and speed, including an inline pause marker (pause is batch-only) and a pronunciation control combined with a speed other than 1.0; description names the specific rule.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java index db0b1ec..acd717d 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.speak.v2.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -38,11 +36,6 @@ public boolean equals(Object other) { return other instanceof SpeakV2Flush; } - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Speak.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Speak.java index 16150bf..8c8e563 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Speak.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Speak.java @@ -37,7 +37,7 @@ public String getType() { } /** - * @return The input text to synthesize. Inline pause and pronunciation controls are not yet applied; they are stripped from the text before synthesis. + * @return The input text to synthesize. May contain inline pronunciation controls (\{"word": "...", "pronounce": "<IPA>"\}), which are in Early Access. Inline pause controls are supported on the batch (REST) transport only; a pause marker sent over the WebSocket fails the connection with DATA-0002. Pronunciation cannot be combined with a speed other than 1.0: text carrying a pronunciation control on a session opened with speed, or after a Configure that set it, also fails the connection with DATA-0002. See Speed, Pause, Pronunciation. */ @JsonProperty("text") public String getText() { @@ -75,7 +75,7 @@ public static TextStage builder() { public interface TextStage { /** - *

The input text to synthesize. Inline pause and pronunciation controls are not yet applied; they are stripped from the text before synthesis.

+ *

The input text to synthesize. May contain inline pronunciation controls (\{"word": "...", "pronounce": "<IPA>"\}), which are in Early Access. Inline pause controls are supported on the batch (REST) transport only; a pause marker sent over the WebSocket fails the connection with DATA-0002. Pronunciation cannot be combined with a speed other than 1.0: text carrying a pronunciation control on a session opened with speed, or after a Configure that set it, also fails the connection with DATA-0002. See Speed, Pause, Pronunciation.

*/ _FinalStage text(@NotNull String text); @@ -106,7 +106,7 @@ public Builder from(SpeakV2Speak other) { } /** - *

The input text to synthesize. Inline pause and pronunciation controls are not yet applied; they are stripped from the text before synthesis.

+ *

The input text to synthesize. May contain inline pronunciation controls (\{"word": "...", "pronounce": "<IPA>"\}), which are in Early Access. Inline pause controls are supported on the batch (REST) transport only; a pause marker sent over the WebSocket fails the connection with DATA-0002. Pronunciation cannot be combined with a speed other than 1.0: text carrying a pronunciation control on a session opened with speed, or after a Configure that set it, also fails the connection with DATA-0002. See Speed, Pause, Pronunciation.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechInterruptedMetadata.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechInterruptedMetadata.java index da24995..586f3c2 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechInterruptedMetadata.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechInterruptedMetadata.java @@ -79,7 +79,7 @@ public int getBillableCharacterCount() { } /** - * @return Counts of the inline controls the server acted on during the turn. Inline pause and pronunciation controls are not applied at launch — support is coming soon — so every count is currently 0. + * @return Counts of the inline controls the server acted on during the turn. A pronunciation override that triggers an IPA warning is still applied best-effort and counted in pronunciations_applied; the warning is reported separately through a Warning and pronunciation_warnings. */ @JsonProperty("controls_applied") public SpeakV2SpeechInterruptedMetadataControlsApplied getControlsApplied() { @@ -156,7 +156,7 @@ public interface BillableCharacterCountStage { public interface ControlsAppliedStage { /** - *

Counts of the inline controls the server acted on during the turn. Inline pause and pronunciation controls are not applied at launch — support is coming soon — so every count is currently 0.

+ *

Counts of the inline controls the server acted on during the turn. A pronunciation override that triggers an IPA warning is still applied best-effort and counted in pronunciations_applied; the warning is reported separately through a Warning and pronunciation_warnings.

*/ _FinalStage controlsApplied(@NotNull SpeakV2SpeechInterruptedMetadataControlsApplied controlsApplied); } @@ -247,7 +247,7 @@ public ControlsAppliedStage billableCharacterCount(int billableCharacterCount) { } /** - *

Counts of the inline controls the server acted on during the turn. Inline pause and pronunciation controls are not applied at launch — support is coming soon — so every count is currently 0.

+ *

Counts of the inline controls the server acted on during the turn. A pronunciation override that triggers an IPA warning is still applied best-effort and counted in pronunciations_applied; the warning is reported separately through a Warning and pronunciation_warnings.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechInterruptedMetadataControlsApplied.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechInterruptedMetadataControlsApplied.java index 85000d9..0d0e0dc 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechInterruptedMetadataControlsApplied.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechInterruptedMetadataControlsApplied.java @@ -38,7 +38,7 @@ private SpeakV2SpeechInterruptedMetadataControlsApplied( } /** - * @return Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header. Currently always 0. + * @return Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header. */ @JsonProperty("pronunciations_applied") public int getPronunciationsApplied() { @@ -46,7 +46,7 @@ public int getPronunciationsApplied() { } /** - * @return Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Currently always 0. + * @return Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Always 0 on the WebSocket, where inline pause controls are not supported. */ @JsonProperty("breaks_applied") public int getBreaksApplied() { @@ -54,7 +54,7 @@ public int getBreaksApplied() { } /** - * @return Pronunciation entries that triggered a warning (invalid IPA, word too long). Mirrors the Aura-2 dg-pronunciation-warnings REST header. Currently always 0. + * @return Pronunciation entries that triggered a warning (invalid IPA, word too long). On batch requests the corresponding PRON-NNN codes are returned in the dg-warnings response header. */ @JsonProperty("pronunciation_warnings") public int getPronunciationWarnings() { @@ -95,7 +95,7 @@ public static PronunciationsAppliedStage builder() { public interface PronunciationsAppliedStage { /** - *

Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header. Currently always 0.

+ *

Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header.

*/ BreaksAppliedStage pronunciationsApplied(int pronunciationsApplied); @@ -104,14 +104,14 @@ public interface PronunciationsAppliedStage { public interface BreaksAppliedStage { /** - *

Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Currently always 0.

+ *

Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Always 0 on the WebSocket, where inline pause controls are not supported.

*/ PronunciationWarningsStage breaksApplied(int breaksApplied); } public interface PronunciationWarningsStage { /** - *

Pronunciation entries that triggered a warning (invalid IPA, word too long). Mirrors the Aura-2 dg-pronunciation-warnings REST header. Currently always 0.

+ *

Pronunciation entries that triggered a warning (invalid IPA, word too long). On batch requests the corresponding PRON-NNN codes are returned in the dg-warnings response header.

*/ _FinalStage pronunciationWarnings(int pronunciationWarnings); } @@ -147,7 +147,7 @@ public Builder from(SpeakV2SpeechInterruptedMetadataControlsApplied other) { } /** - *

Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header. Currently always 0.

+ *

Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override @@ -158,7 +158,7 @@ public BreaksAppliedStage pronunciationsApplied(int pronunciationsApplied) { } /** - *

Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Currently always 0.

+ *

Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Always 0 on the WebSocket, where inline pause controls are not supported.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override @@ -169,7 +169,7 @@ public PronunciationWarningsStage breaksApplied(int breaksApplied) { } /** - *

Pronunciation entries that triggered a warning (invalid IPA, word too long). Mirrors the Aura-2 dg-pronunciation-warnings REST header. Currently always 0.

+ *

Pronunciation entries that triggered a warning (invalid IPA, word too long). On batch requests the corresponding PRON-NNN codes are returned in the dg-warnings response header.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechMetadata.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechMetadata.java index d982b95..be1ab9f 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechMetadata.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechMetadata.java @@ -87,7 +87,7 @@ public int getBillableCharacterCount() { } /** - * @return Counts of the inline controls the server acted on during the turn. Inline pause and pronunciation controls are not applied at launch — support is coming soon — so every count is currently 0. + * @return Counts of the inline controls the server acted on during the turn. A pronunciation override that triggers an IPA warning is still applied best-effort and counted in pronunciations_applied; the warning is reported separately through a Warning and pronunciation_warnings. */ @JsonProperty("controls_applied") public SpeakV2SpeechMetadataControlsApplied getControlsApplied() { @@ -164,7 +164,7 @@ public interface BillableCharacterCountStage { public interface ControlsAppliedStage { /** - *

Counts of the inline controls the server acted on during the turn. Inline pause and pronunciation controls are not applied at launch — support is coming soon — so every count is currently 0.

+ *

Counts of the inline controls the server acted on during the turn. A pronunciation override that triggers an IPA warning is still applied best-effort and counted in pronunciations_applied; the warning is reported separately through a Warning and pronunciation_warnings.

*/ _FinalStage controlsApplied(@NotNull SpeakV2SpeechMetadataControlsApplied controlsApplied); } @@ -255,7 +255,7 @@ public ControlsAppliedStage billableCharacterCount(int billableCharacterCount) { } /** - *

Counts of the inline controls the server acted on during the turn. Inline pause and pronunciation controls are not applied at launch — support is coming soon — so every count is currently 0.

+ *

Counts of the inline controls the server acted on during the turn. A pronunciation override that triggers an IPA warning is still applied best-effort and counted in pronunciations_applied; the warning is reported separately through a Warning and pronunciation_warnings.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechMetadataControlsApplied.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechMetadataControlsApplied.java index b258f91..ddf185a 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechMetadataControlsApplied.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2SpeechMetadataControlsApplied.java @@ -38,7 +38,7 @@ private SpeakV2SpeechMetadataControlsApplied( } /** - * @return Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header. Currently always 0. + * @return Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header. */ @JsonProperty("pronunciations_applied") public int getPronunciationsApplied() { @@ -46,7 +46,7 @@ public int getPronunciationsApplied() { } /** - * @return Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Currently always 0. + * @return Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Always 0 on the WebSocket, where inline pause controls are not supported. */ @JsonProperty("breaks_applied") public int getBreaksApplied() { @@ -54,7 +54,7 @@ public int getBreaksApplied() { } /** - * @return Pronunciation entries that triggered a warning (invalid IPA, word too long). Mirrors the Aura-2 dg-pronunciation-warnings REST header. Currently always 0. + * @return Pronunciation entries that triggered a warning (invalid IPA, word too long). On batch requests the corresponding PRON-NNN codes are returned in the dg-warnings response header. */ @JsonProperty("pronunciation_warnings") public int getPronunciationWarnings() { @@ -95,7 +95,7 @@ public static PronunciationsAppliedStage builder() { public interface PronunciationsAppliedStage { /** - *

Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header. Currently always 0.

+ *

Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header.

*/ BreaksAppliedStage pronunciationsApplied(int pronunciationsApplied); @@ -104,14 +104,14 @@ public interface PronunciationsAppliedStage { public interface BreaksAppliedStage { /** - *

Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Currently always 0.

+ *

Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Always 0 on the WebSocket, where inline pause controls are not supported.

*/ PronunciationWarningsStage breaksApplied(int breaksApplied); } public interface PronunciationWarningsStage { /** - *

Pronunciation entries that triggered a warning (invalid IPA, word too long). Mirrors the Aura-2 dg-pronunciation-warnings REST header. Currently always 0.

+ *

Pronunciation entries that triggered a warning (invalid IPA, word too long). On batch requests the corresponding PRON-NNN codes are returned in the dg-warnings response header.

*/ _FinalStage pronunciationWarnings(int pronunciationWarnings); } @@ -147,7 +147,7 @@ public Builder from(SpeakV2SpeechMetadataControlsApplied other) { } /** - *

Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header. Currently always 0.

+ *

Pronunciation overrides successfully applied. Mirrors the Aura-2 dg-pronunciations-applied REST header.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override @@ -158,7 +158,7 @@ public BreaksAppliedStage pronunciationsApplied(int pronunciationsApplied) { } /** - *

Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Currently always 0.

+ *

Pause (break) controls successfully applied. Mirrors the Aura-2 dg-breaks-applied REST header. Always 0 on the WebSocket, where inline pause controls are not supported.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override @@ -169,7 +169,7 @@ public PronunciationWarningsStage breaksApplied(int breaksApplied) { } /** - *

Pronunciation entries that triggered a warning (invalid IPA, word too long). Mirrors the Aura-2 dg-pronunciation-warnings REST header. Currently always 0.

+ *

Pronunciation entries that triggered a warning (invalid IPA, word too long). On batch requests the corresponding PRON-NNN codes are returned in the dg-warnings response header.

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Warning.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Warning.java index 0eb0b3c..84332f9 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Warning.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Warning.java @@ -42,7 +42,8 @@ public String getType() { /** * @return Warning code identifying the condition, in SCREAMING_SNAKE_CASE. *

Turn-scoped codes: NO_ACTIVE_SPEECH (a speech-scoped message arrived with no active turn), NO_SYNTHESIZABLE_TEXT (the turn's text was entirely whitespace or punctuation, so it produced no audio and is completed with a zero-duration SpeechMetadata), and SYNTHESIS_RETRYING (a synthesis request failed and is being retried).

- *

Inline-control codes are reserved and not currently emitted, because inline pause and pronunciation controls are not yet applied: BREAKS_LIMIT_EXCEEDED (too many pause controls, or two pauses with no intervening text), BREAK_TOKENS_OUT_OF_RANGE (pause durations outside the range the model supports), BREAK_TOKENS_WITH_INVALID_INCREMENTS (pause durations off the model's supported increment), PRONUNCIATION_WARNINGS (a pronunciation override contained invalid IPA), PRONUNCIATION_TOO_LONG (an IPA string exceeded the length limit), PRONUNCIATIONS_LIMIT_EXCEEDED (too many pronunciation controls in one turn).

+ *

Pronunciation codes: PRONUNCIATION_WARNINGS (a pronunciation override contained invalid IPA; it is still applied best-effort and counted in pronunciations_applied), PRONUNCIATION_TOO_LONG (an IPA string exceeded the length limit), PRONUNCIATIONS_LIMIT_EXCEEDED (too many pronunciation controls in one turn).

+ *

Pause codes (BREAKS_LIMIT_EXCEEDED, BREAK_TOKENS_OUT_OF_RANGE, BREAK_TOKENS_WITH_INVALID_INCREMENTS) are reserved and not emitted: inline pause controls are batch-only, and a pause marker on the WebSocket fails the connection instead.

*

Interrupt-scoped codes, each meaning the Interrupt was ignored: NO_AUDIO_GENERATED (the session has produced no audio yet, so there is nothing to interrupt), INTERRUPT_IN_PROGRESS (an earlier Interrupt is still being processed — at most one is handled at a time), INVALID_INTERRUPT_OFFSET (the playback_offset did not advance past the position a prior interrupt established).

*/ @JsonProperty("code") @@ -91,7 +92,8 @@ public interface CodeStage { /** *

Warning code identifying the condition, in SCREAMING_SNAKE_CASE.

*

Turn-scoped codes: NO_ACTIVE_SPEECH (a speech-scoped message arrived with no active turn), NO_SYNTHESIZABLE_TEXT (the turn's text was entirely whitespace or punctuation, so it produced no audio and is completed with a zero-duration SpeechMetadata), and SYNTHESIS_RETRYING (a synthesis request failed and is being retried).

- *

Inline-control codes are reserved and not currently emitted, because inline pause and pronunciation controls are not yet applied: BREAKS_LIMIT_EXCEEDED (too many pause controls, or two pauses with no intervening text), BREAK_TOKENS_OUT_OF_RANGE (pause durations outside the range the model supports), BREAK_TOKENS_WITH_INVALID_INCREMENTS (pause durations off the model's supported increment), PRONUNCIATION_WARNINGS (a pronunciation override contained invalid IPA), PRONUNCIATION_TOO_LONG (an IPA string exceeded the length limit), PRONUNCIATIONS_LIMIT_EXCEEDED (too many pronunciation controls in one turn).

+ *

Pronunciation codes: PRONUNCIATION_WARNINGS (a pronunciation override contained invalid IPA; it is still applied best-effort and counted in pronunciations_applied), PRONUNCIATION_TOO_LONG (an IPA string exceeded the length limit), PRONUNCIATIONS_LIMIT_EXCEEDED (too many pronunciation controls in one turn).

+ *

Pause codes (BREAKS_LIMIT_EXCEEDED, BREAK_TOKENS_OUT_OF_RANGE, BREAK_TOKENS_WITH_INVALID_INCREMENTS) are reserved and not emitted: inline pause controls are batch-only, and a pause marker on the WebSocket fails the connection instead.

*

Interrupt-scoped codes, each meaning the Interrupt was ignored: NO_AUDIO_GENERATED (the session has produced no audio yet, so there is nothing to interrupt), INTERRUPT_IN_PROGRESS (an earlier Interrupt is still being processed — at most one is handled at a time), INVALID_INTERRUPT_OFFSET (the playback_offset did not advance past the position a prior interrupt established).

*/ DescriptionStage code(@NotNull String code); @@ -135,7 +137,8 @@ public Builder from(SpeakV2Warning other) { /** *

Warning code identifying the condition, in SCREAMING_SNAKE_CASE.

*

Turn-scoped codes: NO_ACTIVE_SPEECH (a speech-scoped message arrived with no active turn), NO_SYNTHESIZABLE_TEXT (the turn's text was entirely whitespace or punctuation, so it produced no audio and is completed with a zero-duration SpeechMetadata), and SYNTHESIS_RETRYING (a synthesis request failed and is being retried).

- *

Inline-control codes are reserved and not currently emitted, because inline pause and pronunciation controls are not yet applied: BREAKS_LIMIT_EXCEEDED (too many pause controls, or two pauses with no intervening text), BREAK_TOKENS_OUT_OF_RANGE (pause durations outside the range the model supports), BREAK_TOKENS_WITH_INVALID_INCREMENTS (pause durations off the model's supported increment), PRONUNCIATION_WARNINGS (a pronunciation override contained invalid IPA), PRONUNCIATION_TOO_LONG (an IPA string exceeded the length limit), PRONUNCIATIONS_LIMIT_EXCEEDED (too many pronunciation controls in one turn).

+ *

Pronunciation codes: PRONUNCIATION_WARNINGS (a pronunciation override contained invalid IPA; it is still applied best-effort and counted in pronunciations_applied), PRONUNCIATION_TOO_LONG (an IPA string exceeded the length limit), PRONUNCIATIONS_LIMIT_EXCEEDED (too many pronunciation controls in one turn).

+ *

Pause codes (BREAKS_LIMIT_EXCEEDED, BREAK_TOKENS_OUT_OF_RANGE, BREAK_TOKENS_WITH_INVALID_INCREMENTS) are reserved and not emitted: inline pause controls are batch-only, and a pause marker on the WebSocket fails the connection instead.

*

Interrupt-scoped codes, each meaning the Interrupt was ignored: NO_AUDIO_GENERATED (the session has produced no audio yet, so there is nothing to interrupt), INTERRUPT_IN_PROGRESS (an earlier Interrupt is still being processed — at most one is handled at a time), INVALID_INTERRUPT_OFFSET (the playback_offset did not advance past the position a prior interrupt established).

* @return Reference to {@code this} so that method calls can be chained together. */ diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java index a94145d..07f6c36 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java @@ -3,12 +3,9 @@ */ package com.deepgram.resources.speak.v2.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -146,14 +143,7 @@ public CompletableFuture connect(V2ConnectOptions options) { "mip_opt_out", String.valueOf(options.getMipOptOut().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); - } - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); + urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); @@ -163,13 +153,22 @@ public CompletableFuture connect(V2ConnectOptions options) { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { readyState = WebSocketReadyState.OPEN; @@ -208,6 +207,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { } } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -216,6 +216,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } @@ -610,8 +611,11 @@ private void handleIncomingMessage(String json) { return; } } - // The raw frame is already delivered to onMessage(String), so ignore unknown typed - // frames for forward compatibility with newly added server control messages. + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException( + "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) + + "... Update your SDK version to support new message types.")); + } } catch (Exception e) { if (onErrorHandler != null) { onErrorHandler.accept(e); diff --git a/src/main/java/com/deepgram/types/DeepgramModel.java b/src/main/java/com/deepgram/types/DeepgramModel.java index 14a797c..fc7c08c 100644 --- a/src/main/java/com/deepgram/types/DeepgramModel.java +++ b/src/main/java/com/deepgram/types/DeepgramModel.java @@ -3,8 +3,6 @@ */ package com.deepgram.types; -// Manual patch - see .fernignore. - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; @@ -15,8 +13,6 @@ public final class DeepgramModel { public static final DeepgramModel FLUX_KELSEY_EN = new DeepgramModel(Value.FLUX_KELSEY_EN, "flux-kelsey-en"); - public static final DeepgramModel FLUX_RENEE_EN = new DeepgramModel(Value.FLUX_RENEE_EN, "flux-renee-en"); - public static final DeepgramModel FLUX_SIENNA_EN = new DeepgramModel(Value.FLUX_SIENNA_EN, "flux-sienna-en"); public static final DeepgramModel AURA2HARMONIA_EN = @@ -255,8 +251,6 @@ public T visit(Visitor visitor) { return visitor.visitAura2HeraEn(); case FLUX_KELSEY_EN: return visitor.visitFluxKelseyEn(); - case FLUX_RENEE_EN: - return visitor.visitFluxReneeEn(); case FLUX_SIENNA_EN: return visitor.visitFluxSiennaEn(); case AURA2HARMONIA_EN: @@ -464,8 +458,6 @@ public static DeepgramModel valueOf(String value) { return AURA2HERA_EN; case "flux-kelsey-en": return FLUX_KELSEY_EN; - case "flux-renee-en": - return FLUX_RENEE_EN; case "flux-sienna-en": return FLUX_SIENNA_EN; case "aura-2-harmonia-en": @@ -848,8 +840,6 @@ public enum Value { FLUX_PRIYA_EN, - FLUX_RENEE_EN, - FLUX_RUFUS_EN, FLUX_SEAN_EN, @@ -1052,8 +1042,6 @@ public interface Visitor { T visitFluxPriyaEn(); - T visitFluxReneeEn(); - T visitFluxRufusEn(); T visitFluxSeanEn(); diff --git a/src/main/java/com/deepgram/types/SharedIntents.java b/src/main/java/com/deepgram/types/SharedIntents.java index c689861..05799c2 100644 --- a/src/main/java/com/deepgram/types/SharedIntents.java +++ b/src/main/java/com/deepgram/types/SharedIntents.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -20,18 +21,19 @@ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = SharedIntents.Builder.class) public final class SharedIntents { - private final Optional results; + private final Optional> segments; private final Map additionalProperties; - private SharedIntents(Optional results, Map additionalProperties) { - this.results = results; + private SharedIntents( + Optional> segments, Map additionalProperties) { + this.segments = segments; this.additionalProperties = additionalProperties; } - @JsonProperty("results") - public Optional getResults() { - return results; + @JsonProperty("segments") + public Optional> getSegments() { + return segments; } @java.lang.Override @@ -46,12 +48,12 @@ public Map getAdditionalProperties() { } private boolean equalTo(SharedIntents other) { - return results.equals(other.results); + return segments.equals(other.segments); } @java.lang.Override public int hashCode() { - return Objects.hash(this.results); + return Objects.hash(this.segments); } @java.lang.Override @@ -65,7 +67,7 @@ public static Builder builder() { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder { - private Optional results = Optional.empty(); + private Optional> segments = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -73,23 +75,23 @@ public static final class Builder { private Builder() {} public Builder from(SharedIntents other) { - results(other.getResults()); + segments(other.getSegments()); return this; } - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public Builder results(Optional results) { - this.results = results; + @JsonSetter(value = "segments", nulls = Nulls.SKIP) + public Builder segments(Optional> segments) { + this.segments = segments; return this; } - public Builder results(SharedIntentsResults results) { - this.results = Optional.ofNullable(results); + public Builder segments(List segments) { + this.segments = Optional.ofNullable(segments); return this; } public SharedIntents build() { - return new SharedIntents(results, additionalProperties); + return new SharedIntents(segments, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedIntentsResults.java b/src/main/java/com/deepgram/types/SharedIntentsResults.java deleted file mode 100644 index 44bfe37..0000000 --- a/src/main/java/com/deepgram/types/SharedIntentsResults.java +++ /dev/null @@ -1,106 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedIntentsResults.Builder.class) -public final class SharedIntentsResults { - private final Optional intents; - - private final Map additionalProperties; - - private SharedIntentsResults( - Optional intents, Map additionalProperties) { - this.intents = intents; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("intents") - public Optional getIntents() { - return intents; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SharedIntentsResults && equalTo((SharedIntentsResults) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SharedIntentsResults other) { - return intents.equals(other.intents); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.intents); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional intents = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SharedIntentsResults other) { - intents(other.getIntents()); - return this; - } - - @JsonSetter(value = "intents", nulls = Nulls.SKIP) - public Builder intents(Optional intents) { - this.intents = intents; - return this; - } - - public Builder intents(SharedIntentsResultsIntents intents) { - this.intents = Optional.ofNullable(intents); - return this; - } - - public SharedIntentsResults build() { - return new SharedIntentsResults(intents, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/types/SharedIntentsResultsIntents.java b/src/main/java/com/deepgram/types/SharedIntentsResultsIntents.java deleted file mode 100644 index a5fe89b..0000000 --- a/src/main/java/com/deepgram/types/SharedIntentsResultsIntents.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedIntentsResultsIntents.Builder.class) -public final class SharedIntentsResultsIntents { - private final Optional> segments; - - private final Map additionalProperties; - - private SharedIntentsResultsIntents( - Optional> segments, - Map additionalProperties) { - this.segments = segments; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("segments") - public Optional> getSegments() { - return segments; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SharedIntentsResultsIntents && equalTo((SharedIntentsResultsIntents) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SharedIntentsResultsIntents other) { - return segments.equals(other.segments); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.segments); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional> segments = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SharedIntentsResultsIntents other) { - segments(other.getSegments()); - return this; - } - - @JsonSetter(value = "segments", nulls = Nulls.SKIP) - public Builder segments(Optional> segments) { - this.segments = segments; - return this; - } - - public Builder segments(List segments) { - this.segments = Optional.ofNullable(segments); - return this; - } - - public SharedIntentsResultsIntents build() { - return new SharedIntentsResultsIntents(segments, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItem.java b/src/main/java/com/deepgram/types/SharedIntentsSegmentsItem.java similarity index 78% rename from src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItem.java rename to src/main/java/com/deepgram/types/SharedIntentsSegmentsItem.java index 694be4f..431b68f 100644 --- a/src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItem.java +++ b/src/main/java/com/deepgram/types/SharedIntentsSegmentsItem.java @@ -19,23 +19,23 @@ import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedIntentsResultsIntentsSegmentsItem.Builder.class) -public final class SharedIntentsResultsIntentsSegmentsItem { +@JsonDeserialize(builder = SharedIntentsSegmentsItem.Builder.class) +public final class SharedIntentsSegmentsItem { private final Optional text; private final Optional startWord; private final Optional endWord; - private final Optional> intents; + private final Optional> intents; private final Map additionalProperties; - private SharedIntentsResultsIntentsSegmentsItem( + private SharedIntentsSegmentsItem( Optional text, Optional startWord, Optional endWord, - Optional> intents, + Optional> intents, Map additionalProperties) { this.text = text; this.startWord = startWord; @@ -60,15 +60,14 @@ public Optional getEndWord() { } @JsonProperty("intents") - public Optional> getIntents() { + public Optional> getIntents() { return intents; } @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof SharedIntentsResultsIntentsSegmentsItem - && equalTo((SharedIntentsResultsIntentsSegmentsItem) other); + return other instanceof SharedIntentsSegmentsItem && equalTo((SharedIntentsSegmentsItem) other); } @JsonAnyGetter @@ -76,7 +75,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(SharedIntentsResultsIntentsSegmentsItem other) { + private boolean equalTo(SharedIntentsSegmentsItem other) { return text.equals(other.text) && startWord.equals(other.startWord) && endWord.equals(other.endWord) @@ -105,14 +104,14 @@ public static final class Builder { private Optional endWord = Optional.empty(); - private Optional> intents = Optional.empty(); + private Optional> intents = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); private Builder() {} - public Builder from(SharedIntentsResultsIntentsSegmentsItem other) { + public Builder from(SharedIntentsSegmentsItem other) { text(other.getText()); startWord(other.getStartWord()); endWord(other.getEndWord()); @@ -154,18 +153,18 @@ public Builder endWord(Double endWord) { } @JsonSetter(value = "intents", nulls = Nulls.SKIP) - public Builder intents(Optional> intents) { + public Builder intents(Optional> intents) { this.intents = intents; return this; } - public Builder intents(List intents) { + public Builder intents(List intents) { this.intents = Optional.ofNullable(intents); return this; } - public SharedIntentsResultsIntentsSegmentsItem build() { - return new SharedIntentsResultsIntentsSegmentsItem(text, startWord, endWord, intents, additionalProperties); + public SharedIntentsSegmentsItem build() { + return new SharedIntentsSegmentsItem(text, startWord, endWord, intents, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItemIntentsItem.java b/src/main/java/com/deepgram/types/SharedIntentsSegmentsItemIntentsItem.java similarity index 81% rename from src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItemIntentsItem.java rename to src/main/java/com/deepgram/types/SharedIntentsSegmentsItemIntentsItem.java index d6554e2..9f97c91 100644 --- a/src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItemIntentsItem.java +++ b/src/main/java/com/deepgram/types/SharedIntentsSegmentsItemIntentsItem.java @@ -18,15 +18,15 @@ import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedIntentsResultsIntentsSegmentsItemIntentsItem.Builder.class) -public final class SharedIntentsResultsIntentsSegmentsItemIntentsItem { +@JsonDeserialize(builder = SharedIntentsSegmentsItemIntentsItem.Builder.class) +public final class SharedIntentsSegmentsItemIntentsItem { private final Optional intent; private final Optional confidenceScore; private final Map additionalProperties; - private SharedIntentsResultsIntentsSegmentsItemIntentsItem( + private SharedIntentsSegmentsItemIntentsItem( Optional intent, Optional confidenceScore, Map additionalProperties) { this.intent = intent; this.confidenceScore = confidenceScore; @@ -46,8 +46,8 @@ public Optional getConfidenceScore() { @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof SharedIntentsResultsIntentsSegmentsItemIntentsItem - && equalTo((SharedIntentsResultsIntentsSegmentsItemIntentsItem) other); + return other instanceof SharedIntentsSegmentsItemIntentsItem + && equalTo((SharedIntentsSegmentsItemIntentsItem) other); } @JsonAnyGetter @@ -55,7 +55,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(SharedIntentsResultsIntentsSegmentsItemIntentsItem other) { + private boolean equalTo(SharedIntentsSegmentsItemIntentsItem other) { return intent.equals(other.intent) && confidenceScore.equals(other.confidenceScore); } @@ -84,7 +84,7 @@ public static final class Builder { private Builder() {} - public Builder from(SharedIntentsResultsIntentsSegmentsItemIntentsItem other) { + public Builder from(SharedIntentsSegmentsItemIntentsItem other) { intent(other.getIntent()); confidenceScore(other.getConfidenceScore()); return this; @@ -112,9 +112,8 @@ public Builder confidenceScore(Float confidenceScore) { return this; } - public SharedIntentsResultsIntentsSegmentsItemIntentsItem build() { - return new SharedIntentsResultsIntentsSegmentsItemIntentsItem( - intent, confidenceScore, additionalProperties); + public SharedIntentsSegmentsItemIntentsItem build() { + return new SharedIntentsSegmentsItemIntentsItem(intent, confidenceScore, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedTopics.java b/src/main/java/com/deepgram/types/SharedTopics.java index 463e84c..25a75ea 100644 --- a/src/main/java/com/deepgram/types/SharedTopics.java +++ b/src/main/java/com/deepgram/types/SharedTopics.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -20,18 +21,18 @@ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = SharedTopics.Builder.class) public final class SharedTopics { - private final Optional results; + private final Optional> segments; private final Map additionalProperties; - private SharedTopics(Optional results, Map additionalProperties) { - this.results = results; + private SharedTopics(Optional> segments, Map additionalProperties) { + this.segments = segments; this.additionalProperties = additionalProperties; } - @JsonProperty("results") - public Optional getResults() { - return results; + @JsonProperty("segments") + public Optional> getSegments() { + return segments; } @java.lang.Override @@ -46,12 +47,12 @@ public Map getAdditionalProperties() { } private boolean equalTo(SharedTopics other) { - return results.equals(other.results); + return segments.equals(other.segments); } @java.lang.Override public int hashCode() { - return Objects.hash(this.results); + return Objects.hash(this.segments); } @java.lang.Override @@ -65,7 +66,7 @@ public static Builder builder() { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder { - private Optional results = Optional.empty(); + private Optional> segments = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -73,23 +74,23 @@ public static final class Builder { private Builder() {} public Builder from(SharedTopics other) { - results(other.getResults()); + segments(other.getSegments()); return this; } - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public Builder results(Optional results) { - this.results = results; + @JsonSetter(value = "segments", nulls = Nulls.SKIP) + public Builder segments(Optional> segments) { + this.segments = segments; return this; } - public Builder results(SharedTopicsResults results) { - this.results = Optional.ofNullable(results); + public Builder segments(List segments) { + this.segments = Optional.ofNullable(segments); return this; } public SharedTopics build() { - return new SharedTopics(results, additionalProperties); + return new SharedTopics(segments, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedTopicsResults.java b/src/main/java/com/deepgram/types/SharedTopicsResults.java deleted file mode 100644 index d25c8cc..0000000 --- a/src/main/java/com/deepgram/types/SharedTopicsResults.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedTopicsResults.Builder.class) -public final class SharedTopicsResults { - private final Optional topics; - - private final Map additionalProperties; - - private SharedTopicsResults(Optional topics, Map additionalProperties) { - this.topics = topics; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("topics") - public Optional getTopics() { - return topics; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SharedTopicsResults && equalTo((SharedTopicsResults) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SharedTopicsResults other) { - return topics.equals(other.topics); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.topics); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional topics = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SharedTopicsResults other) { - topics(other.getTopics()); - return this; - } - - @JsonSetter(value = "topics", nulls = Nulls.SKIP) - public Builder topics(Optional topics) { - this.topics = topics; - return this; - } - - public Builder topics(SharedTopicsResultsTopics topics) { - this.topics = Optional.ofNullable(topics); - return this; - } - - public SharedTopicsResults build() { - return new SharedTopicsResults(topics, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/types/SharedTopicsResultsTopics.java b/src/main/java/com/deepgram/types/SharedTopicsResultsTopics.java deleted file mode 100644 index e3a1cbb..0000000 --- a/src/main/java/com/deepgram/types/SharedTopicsResultsTopics.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedTopicsResultsTopics.Builder.class) -public final class SharedTopicsResultsTopics { - private final Optional> segments; - - private final Map additionalProperties; - - private SharedTopicsResultsTopics( - Optional> segments, Map additionalProperties) { - this.segments = segments; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("segments") - public Optional> getSegments() { - return segments; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SharedTopicsResultsTopics && equalTo((SharedTopicsResultsTopics) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SharedTopicsResultsTopics other) { - return segments.equals(other.segments); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.segments); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional> segments = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SharedTopicsResultsTopics other) { - segments(other.getSegments()); - return this; - } - - @JsonSetter(value = "segments", nulls = Nulls.SKIP) - public Builder segments(Optional> segments) { - this.segments = segments; - return this; - } - - public Builder segments(List segments) { - this.segments = Optional.ofNullable(segments); - return this; - } - - public SharedTopicsResultsTopics build() { - return new SharedTopicsResultsTopics(segments, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItem.java b/src/main/java/com/deepgram/types/SharedTopicsSegmentsItem.java similarity index 78% rename from src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItem.java rename to src/main/java/com/deepgram/types/SharedTopicsSegmentsItem.java index 19cd942..b479076 100644 --- a/src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItem.java +++ b/src/main/java/com/deepgram/types/SharedTopicsSegmentsItem.java @@ -19,23 +19,23 @@ import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedTopicsResultsTopicsSegmentsItem.Builder.class) -public final class SharedTopicsResultsTopicsSegmentsItem { +@JsonDeserialize(builder = SharedTopicsSegmentsItem.Builder.class) +public final class SharedTopicsSegmentsItem { private final Optional text; private final Optional startWord; private final Optional endWord; - private final Optional> topics; + private final Optional> topics; private final Map additionalProperties; - private SharedTopicsResultsTopicsSegmentsItem( + private SharedTopicsSegmentsItem( Optional text, Optional startWord, Optional endWord, - Optional> topics, + Optional> topics, Map additionalProperties) { this.text = text; this.startWord = startWord; @@ -60,15 +60,14 @@ public Optional getEndWord() { } @JsonProperty("topics") - public Optional> getTopics() { + public Optional> getTopics() { return topics; } @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof SharedTopicsResultsTopicsSegmentsItem - && equalTo((SharedTopicsResultsTopicsSegmentsItem) other); + return other instanceof SharedTopicsSegmentsItem && equalTo((SharedTopicsSegmentsItem) other); } @JsonAnyGetter @@ -76,7 +75,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(SharedTopicsResultsTopicsSegmentsItem other) { + private boolean equalTo(SharedTopicsSegmentsItem other) { return text.equals(other.text) && startWord.equals(other.startWord) && endWord.equals(other.endWord) @@ -105,14 +104,14 @@ public static final class Builder { private Optional endWord = Optional.empty(); - private Optional> topics = Optional.empty(); + private Optional> topics = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); private Builder() {} - public Builder from(SharedTopicsResultsTopicsSegmentsItem other) { + public Builder from(SharedTopicsSegmentsItem other) { text(other.getText()); startWord(other.getStartWord()); endWord(other.getEndWord()); @@ -154,18 +153,18 @@ public Builder endWord(Double endWord) { } @JsonSetter(value = "topics", nulls = Nulls.SKIP) - public Builder topics(Optional> topics) { + public Builder topics(Optional> topics) { this.topics = topics; return this; } - public Builder topics(List topics) { + public Builder topics(List topics) { this.topics = Optional.ofNullable(topics); return this; } - public SharedTopicsResultsTopicsSegmentsItem build() { - return new SharedTopicsResultsTopicsSegmentsItem(text, startWord, endWord, topics, additionalProperties); + public SharedTopicsSegmentsItem build() { + return new SharedTopicsSegmentsItem(text, startWord, endWord, topics, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItemTopicsItem.java b/src/main/java/com/deepgram/types/SharedTopicsSegmentsItemTopicsItem.java similarity index 82% rename from src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItemTopicsItem.java rename to src/main/java/com/deepgram/types/SharedTopicsSegmentsItemTopicsItem.java index 3721cd8..03464d2 100644 --- a/src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItemTopicsItem.java +++ b/src/main/java/com/deepgram/types/SharedTopicsSegmentsItemTopicsItem.java @@ -18,15 +18,15 @@ import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedTopicsResultsTopicsSegmentsItemTopicsItem.Builder.class) -public final class SharedTopicsResultsTopicsSegmentsItemTopicsItem { +@JsonDeserialize(builder = SharedTopicsSegmentsItemTopicsItem.Builder.class) +public final class SharedTopicsSegmentsItemTopicsItem { private final Optional topic; private final Optional confidenceScore; private final Map additionalProperties; - private SharedTopicsResultsTopicsSegmentsItemTopicsItem( + private SharedTopicsSegmentsItemTopicsItem( Optional topic, Optional confidenceScore, Map additionalProperties) { this.topic = topic; this.confidenceScore = confidenceScore; @@ -46,8 +46,8 @@ public Optional getConfidenceScore() { @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof SharedTopicsResultsTopicsSegmentsItemTopicsItem - && equalTo((SharedTopicsResultsTopicsSegmentsItemTopicsItem) other); + return other instanceof SharedTopicsSegmentsItemTopicsItem + && equalTo((SharedTopicsSegmentsItemTopicsItem) other); } @JsonAnyGetter @@ -55,7 +55,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(SharedTopicsResultsTopicsSegmentsItemTopicsItem other) { + private boolean equalTo(SharedTopicsSegmentsItemTopicsItem other) { return topic.equals(other.topic) && confidenceScore.equals(other.confidenceScore); } @@ -84,7 +84,7 @@ public static final class Builder { private Builder() {} - public Builder from(SharedTopicsResultsTopicsSegmentsItemTopicsItem other) { + public Builder from(SharedTopicsSegmentsItemTopicsItem other) { topic(other.getTopic()); confidenceScore(other.getConfidenceScore()); return this; @@ -112,8 +112,8 @@ public Builder confidenceScore(Float confidenceScore) { return this; } - public SharedTopicsResultsTopicsSegmentsItemTopicsItem build() { - return new SharedTopicsResultsTopicsSegmentsItemTopicsItem(topic, confidenceScore, additionalProperties); + public SharedTopicsSegmentsItemTopicsItem build() { + return new SharedTopicsSegmentsItemTopicsItem(topic, confidenceScore, additionalProperties); } public Builder additionalProperty(String key, Object value) { From 396d6a6debd8df0172546aee1752966c14961065 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 13:42:17 +0100 Subject: [PATCH 04/14] chore: re-apply manual patches after regen --- .fernignore | 51 +- .../java/com/deepgram/core/ClientOptions.java | 9 +- .../com/deepgram/core/ClientOptions.java.bak | 340 ------ .../core/ReconnectingWebSocketListener.java | 61 +- .../ReconnectingWebSocketListener.java.bak | 590 --------- .../agent/v1/types/AgentV1AgentAudioDone.java | 5 + .../v1/types/AgentV1AgentAudioDone.java.bak | 85 -- .../agent/v1/types/AgentV1ForceEndTurn.java | 5 + .../v1/types/AgentV1ForceEndTurn.java.bak | 85 -- .../agent/v1/types/AgentV1KeepAlive.java | 5 + .../agent/v1/types/AgentV1KeepAlive.java.bak | 85 -- .../agent/v1/types/AgentV1ListenUpdated.java | 5 + .../v1/types/AgentV1ListenUpdated.java.bak | 85 -- .../agent/v1/types/AgentV1PromptUpdated.java | 5 + .../v1/types/AgentV1PromptUpdated.java.bak | 85 -- ...tV1SettingsAgentContextListenProvider.java | 2 +- ...ettingsAgentContextListenProvider.java.bak | 226 ---- .../AgentV1SettingsAgentListenProvider.java | 2 +- ...gentV1SettingsAgentListenProvider.java.bak | 226 ---- .../v1/types/AgentV1SettingsApplied.java | 5 + .../v1/types/AgentV1SettingsApplied.java.bak | 85 -- .../agent/v1/types/AgentV1SpeakUpdated.java | 5 + .../v1/types/AgentV1SpeakUpdated.java.bak | 85 -- .../agent/v1/types/AgentV1ThinkUpdated.java | 5 + .../v1/types/AgentV1ThinkUpdated.java.bak | 85 -- .../AgentV1UpdateListenListenProvider.java | 2 +- ...AgentV1UpdateListenListenProvider.java.bak | 226 ---- .../v1/types/AgentV1UserStartedSpeaking.java | 5 + .../types/AgentV1UserStartedSpeaking.java.bak | 85 -- .../v1/websocket/V1WebSocketClient.java.bak | 868 ------------- .../v1/websocket/V1WebSocketClient.java | 25 +- .../v1/websocket/V1WebSocketClient.java.bak | 545 --------- .../listen/v2/types/ListenV2CloseStream.java | 5 + .../v2/types/ListenV2CloseStream.java.bak | 85 -- .../listen/v2/types/ListenV2ForceEndTurn.java | 5 + .../v2/types/ListenV2ForceEndTurn.java.bak | 85 -- .../v2/websocket/V2WebSocketClient.java | 47 +- .../v2/websocket/V2WebSocketClient.java.bak | 532 -------- .../speak/v1/websocket/V1WebSocketClient.java | 8 + .../v1/websocket/V1WebSocketClient.java.bak | 458 ------- .../speak/v2/types/SpeakV2Close.java | 5 + .../speak/v2/types/SpeakV2Close.java.bak | 85 -- .../speak/v2/types/SpeakV2Flush.java | 5 + .../speak/v2/types/SpeakV2Flush.java.bak | 85 -- .../speak/v2/websocket/V2WebSocketClient.java | 17 +- .../v2/websocket/V2WebSocketClient.java.bak | 621 ---------- .../com/deepgram/types/DeepgramModel.java | 10 + .../com/deepgram/types/DeepgramModel.java.bak | 1073 ----------------- 48 files changed, 212 insertions(+), 6897 deletions(-) delete mode 100644 src/main/java/com/deepgram/core/ClientOptions.java.bak delete mode 100644 src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak delete mode 100644 src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak delete mode 100644 src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak delete mode 100644 src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak delete mode 100644 src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak delete mode 100644 src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak delete mode 100644 src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak delete mode 100644 src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak delete mode 100644 src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak delete mode 100644 src/main/java/com/deepgram/types/DeepgramModel.java.bak diff --git a/.fernignore b/.fernignore index babb6bf..918d871 100644 --- a/.fernignore +++ b/.fernignore @@ -15,7 +15,7 @@ src/main/java/com/deepgram/AsyncDeepgramClientBuilder.java # with // x-release-please-version comments for automated version bumps. # Fern regen overwrites these with incorrect SDK names and strips the markers. # Fern 4.21.0 owns the OkHttp lifecycle behavior; retain it while reapplying only these headers. -src/main/java/com/deepgram/core/ClientOptions.java.bak +src/main/java/com/deepgram/core/ClientOptions.java # Transport abstraction (pluggable transport for SageMaker, etc.) src/main/java/com/deepgram/core/transport/ @@ -23,7 +23,7 @@ src/main/java/com/deepgram/core/transport/ # Bug fixes for maxRetries(0) semantics ("connect once, don't retry") and a # configurable connectionTimeoutMs on ReconnectOptions (was hardcoded 4000ms). # Pull this back out once the fixes are upstreamed into the Fern generator. -src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak +src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java # Forward-compat patch: Fern's generated dispatcher routes any unrecognized message # type to the error handler ("Update your SDK version..."), which makes benign new @@ -33,8 +33,8 @@ src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak # NOTE: the listen v2 and speak v2 clients below also carry the streaming query-param patches # (multi-value serialization + the additionalProperties escape hatch) documented lower down — # keep them frozen until those fixes are upstreamed too. -src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak -src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak +src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java +src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java # Streaming query-param patches on the generated WS connect() builders. Two fixes: # (1) Multi-value serialization: array-valued params (listen: keyterm, keywords, replace, search, @@ -48,13 +48,8 @@ src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java. # listen v2 and speak v2 clients are frozen above; freeze the listen v1 and speak v1 clients here too # so the fixes survive regen. Re-apply after regen; unfreeze once the generator emits array params as # repeats and serializes additionalProperties on the WS connect path (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak -src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak - -# Close is safe before connect in every generated WebSocket client. The listener is created only -# during connect(), so disconnect() must tolerate a null listener for try-with-resources clients. -# Remove these patches once Fern emits the null guard. -src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak +src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java +src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java # Restores the FLUX_RENEE_EN constant that generator 4.18.0 dropped. The voice is live: # POST /v2/speak?model=flux-renee-en returns 200 with valid audio, and the name resolves in the @@ -66,25 +61,25 @@ src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.b # changes -- 4.18.0 alone added 25 constants. On the next regen do NOT restore the .bak wholesale: # diff it against the newly generated file, carry forward every new voice, and re-apply only the # FLUX_RENEE_EN touchpoints (constant, Value enum, visit() case, valueOf() case, Visitor method). -src/main/java/com/deepgram/types/DeepgramModel.java.bak +src/main/java/com/deepgram/types/DeepgramModel.java # Manual equals/hashCode contract fix: Fern generates equals() (all instances equal) but no # hashCode() for fields-less message types, violating the Object contract. We add a consistent # hashCode() to each. Unfreeze and drop these patches once the generator emits a matching # equals/hashCode pair for fields-less types (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak -src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak -src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak -src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak +src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java +src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java +src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java +src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java # Union default-variant fix (STOPGAP). The agent listen-provider unions declare `version` as an # optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits). Fern @@ -93,9 +88,9 @@ src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak # {"provider":null}, silently dropping the caller's provider on the wire. Patched to # defaultImpl = V2Value on each. Guarded by AgentSettingsProviderDefaultTest. Unfreeze and drop once # the generator stops defaulting unions to the empty _UnknownValue (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java # Build and project configuration build.gradle diff --git a/src/main/java/com/deepgram/core/ClientOptions.java b/src/main/java/com/deepgram/core/ClientOptions.java index e6cfae7..bfb382f 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -63,10 +63,10 @@ private ClientOptions( this.headers.putAll(headers); this.headers.putAll(new HashMap() { { - put("User-Agent", "com.deepgram.deepgram-java-sdk/" + getSdkVersion()); + put("User-Agent", "com.deepgram:deepgram-java-sdk/0.10.2"); // x-release-please-version put("X-Fern-Language", "JAVA"); put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); - put("X-Fern-SDK-Version", getSdkVersion()); + put("X-Fern-SDK-Version", "0.10.2"); // x-release-please-version } }); this.headerSuppliers = headerSuppliers; @@ -98,11 +98,6 @@ public Map headers(RequestOptions requestOptions) { return values; } - private static String getSdkVersion() { - String version = ClientOptions.class.getPackage().getImplementationVersion(); - return version != null ? version : "0.10.3"; - } - public int timeout(RequestOptions requestOptions) { if (requestOptions == null) { return this.timeout; diff --git a/src/main/java/com/deepgram/core/ClientOptions.java.bak b/src/main/java/com/deepgram/core/ClientOptions.java.bak deleted file mode 100644 index 5329810..0000000 --- a/src/main/java/com/deepgram/core/ClientOptions.java.bak +++ /dev/null @@ -1,340 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.core; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import okhttp3.OkHttpClient; - -public final class ClientOptions { - private final Environment environment; - - private final Map headers; - - private final Map> headerSuppliers; - - private final OkHttpClient httpClient; - - private final boolean ownsHttpClient; - - private final int timeout; - - private final int maxRetries; - - private final Optional initialRetryDelayMillis; - - private final Optional maxRetryDelayMillis; - - private final Optional retryJitterFactor; - - private final Optional webSocketFactory; - - private final Optional logging; - - private ClientOptions( - Environment environment, - Map headers, - Map> headerSuppliers, - OkHttpClient httpClient, - boolean ownsHttpClient, - int timeout, - int maxRetries, - Optional initialRetryDelayMillis, - Optional maxRetryDelayMillis, - Optional retryJitterFactor, - Optional webSocketFactory, - Optional logging) { - this.environment = environment; - this.headers = new HashMap<>(); - this.headers.putAll(headers); - this.headers.putAll(new HashMap() { - { - put("User-Agent", "com.deepgram:deepgram-java-sdk/0.10.2"); // x-release-please-version - put("X-Fern-Language", "JAVA"); - put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); - put("X-Fern-SDK-Version", "0.10.2"); // x-release-please-version - } - }); - this.headerSuppliers = headerSuppliers; - this.httpClient = httpClient; - this.ownsHttpClient = ownsHttpClient; - this.timeout = timeout; - this.maxRetries = maxRetries; - this.initialRetryDelayMillis = initialRetryDelayMillis; - this.maxRetryDelayMillis = maxRetryDelayMillis; - this.retryJitterFactor = retryJitterFactor; - this.webSocketFactory = webSocketFactory; - this.logging = logging; - } - - public Environment environment() { - return this.environment; - } - - public Map headers(RequestOptions requestOptions) { - Map values = new HashMap<>(this.headers); - headerSuppliers.forEach((key, supplier) -> { - values.put(key, supplier.get()); - }); - if (requestOptions != null) { - values.putAll(requestOptions.getHeaders()); - } - return values; - } - - public int timeout(RequestOptions requestOptions) { - if (requestOptions == null) { - return this.timeout; - } - return requestOptions.getTimeout().orElse(this.timeout); - } - - public OkHttpClient httpClient() { - return this.httpClient; - } - - public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { - if (requestOptions == null) { - return this.httpClient; - } - return this.httpClient - .newBuilder() - .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS) - .build(); - } - - public int maxRetries() { - return this.maxRetries; - } - - public Optional initialRetryDelayMillis() { - return this.initialRetryDelayMillis; - } - - public Optional maxRetryDelayMillis() { - return this.maxRetryDelayMillis; - } - - public Optional retryJitterFactor() { - return this.retryJitterFactor; - } - - /** - * Releases resources owned by this client. Only shuts down the underlying OkHttpClient's - * dispatcher executor and evicts its connection pool when this client created that - * OkHttpClient itself; an OkHttpClient supplied via httpClient is left running, since the - * caller owns its lifecycle. - *

- * In-flight calls are not cancelled or awaited. Later asynchronous requests fail when the - * dispatcher rejects work, while synchronous requests may still run. Options derived from this - * one via {@code Builder.from(...)} share the same dispatcher and connection pool, so closing - * either releases them for both. Calling this method more than once has no further effect. - */ - public void close() { - if (!this.ownsHttpClient) { - return; - } - this.httpClient.dispatcher().executorService().shutdown(); - this.httpClient.connectionPool().evictAll(); - } - - public Optional webSocketFactory() { - return this.webSocketFactory; - } - - public Optional logging() { - return this.logging; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Environment environment; - - private final Map headers = new HashMap<>(); - - private final Map> headerSuppliers = new HashMap<>(); - - private int maxRetries = 2; - - private Optional initialRetryDelayMillis = Optional.empty(); - - private Optional maxRetryDelayMillis = Optional.empty(); - - private Optional retryJitterFactor = Optional.empty(); - - private Optional timeout = Optional.empty(); - - private OkHttpClient httpClient = null; - - private boolean ownsHttpClient = true; - - private Optional logging = Optional.empty(); - - private Optional webSocketFactory = Optional.empty(); - - public Builder environment(Environment environment) { - this.environment = environment; - return this; - } - - public Builder addHeader(String key, String value) { - if (value != null) { - this.headers.put(key, value); - } - return this; - } - - public Builder addHeader(String key, Supplier value) { - this.headerSuppliers.put(key, value); - return this; - } - - /** - * Override the timeout in seconds. Defaults to 60 seconds. - */ - public Builder timeout(int timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - /** - * Override the timeout in seconds. Defaults to 60 seconds. - */ - public Builder timeout(Optional timeout) { - this.timeout = timeout; - return this; - } - - /** - * Override the maximum number of retries. Defaults to 2 retries. - */ - public Builder maxRetries(int maxRetries) { - this.maxRetries = maxRetries; - return this; - } - - /** - * Override the initial delay (in milliseconds) used for exponential backoff between retries. Defaults to 1000 milliseconds. - */ - public Builder initialRetryDelayMillis(long initialRetryDelayMillis) { - this.initialRetryDelayMillis = Optional.of(initialRetryDelayMillis); - return this; - } - - /** - * Override the maximum delay (in milliseconds) between retries. Defaults to 60000 milliseconds. - */ - public Builder maxRetryDelayMillis(long maxRetryDelayMillis) { - this.maxRetryDelayMillis = Optional.of(maxRetryDelayMillis); - return this; - } - - /** - * Override the jitter factor (between 0 and 1) applied to retry delays. Defaults to 0.2. - */ - public Builder retryJitterFactor(double retryJitterFactor) { - this.retryJitterFactor = Optional.of(retryJitterFactor); - return this; - } - - /** - * Sets the underlying OkHttp client. The caller retains ownership of its lifecycle: - * close() will not shut down its dispatcher executor or evict its connection pool. - */ - public Builder httpClient(OkHttpClient httpClient) { - this.httpClient = httpClient; - this.ownsHttpClient = httpClient == null; - return this; - } - - /** - * Set a custom WebSocketFactory for creating WebSocket connections. - */ - public Builder webSocketFactory(WebSocketFactory webSocketFactory) { - this.webSocketFactory = Optional.of(webSocketFactory); - return this; - } - - /** - * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. - */ - public Builder logging(LogConfig logging) { - this.logging = Optional.of(logging); - return this; - } - - public ClientOptions build() { - OkHttpClient.Builder httpClientBuilder = - this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); - - if (this.httpClient != null) { - timeout.ifPresent(timeout -> httpClientBuilder - .callTimeout(timeout, TimeUnit.SECONDS) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS)); - } else { - httpClientBuilder - .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS) - .addInterceptor(new RetryInterceptor( - this.maxRetries, - this.initialRetryDelayMillis, - this.maxRetryDelayMillis, - this.retryJitterFactor)); - } - - Logger logger = Logger.from(this.logging); - httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); - httpClientBuilder.addInterceptor(new ResponseDecompressionInterceptor()); - - this.httpClient = httpClientBuilder.build(); - this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); - - return new ClientOptions( - environment, - headers, - headerSuppliers, - httpClient, - this.ownsHttpClient, - this.timeout.get(), - this.maxRetries, - this.initialRetryDelayMillis, - this.maxRetryDelayMillis, - this.retryJitterFactor, - this.webSocketFactory, - this.logging); - } - - /** - * Create a new Builder initialized with values from an existing ClientOptions - */ - public static Builder from(ClientOptions clientOptions) { - Builder builder = new Builder(); - builder.environment = clientOptions.environment(); - builder.timeout = Optional.of(clientOptions.timeout(null)); - builder.httpClient = clientOptions.httpClient(); - builder.ownsHttpClient = clientOptions.ownsHttpClient; - builder.headers.putAll(clientOptions.headers); - builder.headerSuppliers.putAll(clientOptions.headerSuppliers); - builder.maxRetries = clientOptions.maxRetries(); - builder.initialRetryDelayMillis = clientOptions.initialRetryDelayMillis(); - builder.maxRetryDelayMillis = clientOptions.maxRetryDelayMillis(); - builder.retryJitterFactor = clientOptions.retryJitterFactor(); - builder.logging = clientOptions.logging(); - return builder; - } - } -} diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java index dc32da2..294316b 100644 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java @@ -27,13 +27,8 @@ * Provides production-ready resilience for WebSocket connections. */ public abstract class ReconnectingWebSocketListener extends WebSocketListener { - private final long minReconnectionDelayMs; - - private final long maxReconnectionDelayMs; - - private final double reconnectionDelayGrowFactor; - - private final int maxRetries; + // A single volatile reference keeps an override internally consistent. + private volatile ReconnectOptions activeOptions; private final int maxEnqueuedMessages; @@ -81,22 +76,30 @@ public ReconnectingWebSocketListener( ReconnectingWebSocketListener.ReconnectOptions options, Supplier connectionSupplier, BooleanSupplier closedCheck) { - this.minReconnectionDelayMs = options.minReconnectionDelayMs; - this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; - this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; - this.maxRetries = options.maxRetries; + this.activeOptions = options; this.maxEnqueuedMessages = options.maxEnqueuedMessages; this.connectionSupplier = connectionSupplier; this.closedCheck = closedCheck; } + /** + * Replaces option-derived reconnect settings without rebuilding generated clients. + * + * @param options replacement options; {@code null} is a no-op + */ + public void applyOptionsOverride(ReconnectOptions options) { + if (options != null) { + this.activeOptions = options; + } + } + /** * Initiates a WebSocket connection with automatic reconnection enabled. * * Connection behavior: - * - Times out after 4000 milliseconds + * - Times out after {@code ReconnectOptions.connectionTimeoutMs} (default 4000ms) * - Thread-safe via atomic lock (returns immediately if connection in progress) - * - Retry count not incremented for initial connection attempt + * - {@code maxRetries} counts retries only; {@code maxRetries(0)} means connect once, do not retry * * Error handling: * - TimeoutException: Includes retry attempt context @@ -114,18 +117,19 @@ public void connect() { onWebSocketFailure(null, new IllegalStateException("root client has been closed"), null); return; } - if (retryCount.get() >= maxRetries) { + ReconnectOptions options = this.activeOptions; + if (retryCount.get() > options.maxRetries) { connectLock.set(false); return; } try { CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); try { - webSocket = connectionFuture.get(4000, MILLISECONDS); + webSocket = connectionFuture.get(options.connectionTimeoutMs, MILLISECONDS); } catch (TimeoutException e) { connectionFuture.cancel(true); TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + 4000 + " milliseconds" + new TimeoutException("WebSocket connection timeout after " + options.connectionTimeoutMs + " milliseconds" + (retryCount.get() > 0 ? " (retry attempt #" + retryCount.get() + ")" : " (initial connection attempt)")); @@ -415,10 +419,12 @@ protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, Strin */ private long getNextDelay() { if (retryCount.get() == 1) { - return minReconnectionDelayMs; + return activeOptions.minReconnectionDelayMs; } - long delay = (long) (minReconnectionDelayMs * Math.pow(reconnectionDelayGrowFactor, retryCount.get() - 1)); - return Math.min(delay, maxReconnectionDelayMs); + ReconnectOptions options = this.activeOptions; + long delay = (long) + (options.minReconnectionDelayMs * Math.pow(options.reconnectionDelayGrowFactor, retryCount.get() - 1)); + return Math.min(delay, options.maxReconnectionDelayMs); } /** @@ -504,12 +510,15 @@ public static final class ReconnectOptions { public final int maxEnqueuedMessages; + public final long connectionTimeoutMs; + private ReconnectOptions(Builder builder) { this.minReconnectionDelayMs = builder.minReconnectionDelayMs; this.maxReconnectionDelayMs = builder.maxReconnectionDelayMs; this.reconnectionDelayGrowFactor = builder.reconnectionDelayGrowFactor; this.maxRetries = builder.maxRetries; this.maxEnqueuedMessages = builder.maxEnqueuedMessages; + this.connectionTimeoutMs = builder.connectionTimeoutMs; } public static Builder builder() { @@ -527,12 +536,15 @@ public static final class Builder { private int maxEnqueuedMessages; + private long connectionTimeoutMs; + public Builder() { this.minReconnectionDelayMs = 1000; this.maxReconnectionDelayMs = 10000; this.reconnectionDelayGrowFactor = 1.3; this.maxRetries = 2147483647; this.maxEnqueuedMessages = 1000; + this.connectionTimeoutMs = 4000; } public Builder minReconnectionDelayMs(long minReconnectionDelayMs) { @@ -560,6 +572,14 @@ public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { return this; } + /** + * Sets the per-attempt connection timeout in milliseconds. Defaults to {@code 4000}. + */ + public Builder connectionTimeoutMs(long connectionTimeoutMs) { + this.connectionTimeoutMs = connectionTimeoutMs; + return this; + } + /** * Builds the ReconnectOptions with validation. * @@ -592,6 +612,9 @@ public ReconnectOptions build() { if (maxEnqueuedMessages < 0) { throw new IllegalArgumentException("maxEnqueuedMessages must be non-negative"); } + if (connectionTimeoutMs <= 0) { + throw new IllegalArgumentException("connectionTimeoutMs must be positive"); + } return new ReconnectOptions(this); } } diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak deleted file mode 100644 index 7f6d9df..0000000 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak +++ /dev/null @@ -1,590 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.core; - -import static java.util.concurrent.TimeUnit.*; - -import java.util.ArrayList; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Supplier; -import okhttp3.Response; -import okhttp3.WebSocket; -import okhttp3.WebSocketListener; -import okio.ByteString; - -/** - * WebSocketListener with automatic reconnection, exponential backoff, and message queuing. - * Provides production-ready resilience for WebSocket connections. - */ -public abstract class ReconnectingWebSocketListener extends WebSocketListener { - // A single volatile reference keeps an override internally consistent. - private volatile ReconnectOptions activeOptions; - - private final int maxEnqueuedMessages; - - private final AtomicInteger retryCount = new AtomicInteger(0); - - private final AtomicBoolean connectLock = new AtomicBoolean(false); - - private final AtomicBoolean shouldReconnect = new AtomicBoolean(true); - - protected volatile WebSocket webSocket; - - private volatile long connectionEstablishedTime = 0L; - - private final ConcurrentLinkedQueue messageQueue = new ConcurrentLinkedQueue<>(); - - private final ConcurrentLinkedQueue binaryMessageQueue = new ConcurrentLinkedQueue<>(); - - private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(); - - private final Supplier connectionSupplier; - - /** - * Creates a new reconnecting WebSocket listener. - * - * @param options Reconnection configuration options - * @param connectionSupplier Supplier that creates new WebSocket connections - */ - public ReconnectingWebSocketListener( - ReconnectingWebSocketListener.ReconnectOptions options, Supplier connectionSupplier) { - this.activeOptions = options; - this.maxEnqueuedMessages = options.maxEnqueuedMessages; - this.connectionSupplier = connectionSupplier; - } - - /** - * Replaces option-derived reconnect settings without rebuilding generated clients. - * - * @param options replacement options; {@code null} is a no-op - */ - public void applyOptionsOverride(ReconnectOptions options) { - if (options != null) { - this.activeOptions = options; - } - } - - /** - * Initiates a WebSocket connection with automatic reconnection enabled. - * - * Connection behavior: - * - Times out after {@code ReconnectOptions.connectionTimeoutMs} (default 4000ms) - * - Thread-safe via atomic lock (returns immediately if connection in progress) - * - {@code maxRetries} counts retries only; {@code maxRetries(0)} means connect once, do not retry - * - * Error handling: - * - TimeoutException: Includes retry attempt context - * - InterruptedException: Preserves thread interruption status - * - ExecutionException: Extracts actual cause and adds context - */ - public void connect() { - if (!connectLock.compareAndSet(false, true)) { - return; - } - ReconnectOptions options = this.activeOptions; - if (retryCount.get() > options.maxRetries) { - connectLock.set(false); - return; - } - try { - CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); - try { - webSocket = connectionFuture.get(options.connectionTimeoutMs, MILLISECONDS); - } catch (TimeoutException e) { - connectionFuture.cancel(true); - TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + options.connectionTimeoutMs + " milliseconds" - + (retryCount.get() > 0 - ? " (retry attempt #" + retryCount.get() + ")" - : " (initial connection attempt)")); - onWebSocketFailure(null, timeoutError, null); - if (shouldReconnect.get()) { - scheduleReconnect(); - } - } catch (InterruptedException e) { - connectionFuture.cancel(true); - Thread.currentThread().interrupt(); - InterruptedException interruptError = new InterruptedException("WebSocket connection interrupted" - + (retryCount.get() > 0 - ? " during retry attempt #" + retryCount.get() - : " during initial connection")); - interruptError.initCause(e); - onWebSocketFailure(null, interruptError, null); - } catch (ExecutionException e) { - Throwable cause = e.getCause() != null ? e.getCause() : e; - String context = retryCount.get() > 0 - ? "WebSocket connection failed during retry attempt #" + retryCount.get() - : "WebSocket connection failed during initial attempt"; - RuntimeException wrappedException = new RuntimeException( - context + ": " + cause.getClass().getSimpleName() + ": " + cause.getMessage()); - wrappedException.initCause(cause); - onWebSocketFailure(null, wrappedException, null); - if (shouldReconnect.get()) { - scheduleReconnect(); - } - } - } finally { - connectLock.set(false); - } - } - - /** - * Disconnects the WebSocket and disables automatic reconnection. - * - * This method: - * - Disables automatic reconnection - * - Clears queued messages to prevent stale data - * - Closes the WebSocket with standard close code 1000 - * - Properly shuts down the reconnect executor to prevent thread leaks - * - Waits up to 5 seconds for executor termination - */ - public void disconnect() { - shouldReconnect.set(false); - messageQueue.clear(); - binaryMessageQueue.clear(); - if (webSocket != null) { - webSocket.close(1000, "Client disconnecting"); - } - reconnectExecutor.shutdown(); - try { - if (!reconnectExecutor.awaitTermination(5, SECONDS)) { - reconnectExecutor.shutdownNow(); - } - } catch (InterruptedException e) { - reconnectExecutor.shutdownNow(); - Thread.currentThread().interrupt(); - } - } - - /** - * Sends a message or queues it if not connected. - * - * Thread-safe: Synchronized to prevent race conditions with flushMessageQueue(). - * - * Behavior: - * - If connected: Attempts direct send, queues if buffer full - * - If disconnected: Queues message up to maxEnqueuedMessages limit - * - If queue full: Message is dropped - * - * @param message The message to send - * @return true if sent immediately, false if queued or dropped - */ - public synchronized boolean send(String message) { - return send(message, null); - } - - /** - * Sends a message or queues it if not connected, exposing the accepting socket. - * - * Behaves like {@link #send(String)}, but additionally invokes {@code onSent} with the - * WebSocket that accepted the message. The callback is only invoked when the message was - * sent directly, never when it was queued or dropped. This lets callers associate a - * protocol-level message with the specific connection it was delivered on, e.g. to decide - * in {@link #shouldReconnectAfterClose(WebSocket, int, String)} whether a later close of that same - * connection was expected. - * - * @param message The message to send - * @param onSent Callback receiving the WebSocket that accepted the message, or null - * @return true if sent immediately, false if queued or dropped - */ - public synchronized boolean send(String message, Consumer onSent) { - WebSocket ws = webSocket; - if (ws != null) { - boolean sent = ws.send(message); - if (sent && onSent != null) { - onSent.accept(ws); - } - if (!sent && messageQueue.size() < maxEnqueuedMessages) { - messageQueue.offer(message); - return false; - } - return sent; - } else { - if (messageQueue.size() < maxEnqueuedMessages) { - messageQueue.offer(message); - return false; - } - return false; - } - } - - /** - * Sends binary data or queues it if not connected. - * - * Thread-safe: Synchronized to prevent race conditions with flushMessageQueue(). - * - * Behavior: - * - If connected: Attempts direct send, queues if buffer full - * - If disconnected: Queues data up to maxEnqueuedMessages limit - * - If queue full: Data is dropped - * - * @param data The binary data to send - * @return true if sent immediately, false if queued or dropped - */ - public synchronized boolean sendBinary(ByteString data) { - return sendBinary(data, null); - } - - /** - * Sends binary data or queues it if not connected, exposing the accepting socket. - * - * Behaves like {@link #sendBinary(ByteString)}, but additionally invokes {@code onSent} with - * the WebSocket that accepted the data. The callback is only invoked when the data was sent - * directly, never when it was queued or dropped. - * - * @param data The binary data to send - * @param onSent Callback receiving the WebSocket that accepted the data, or null - * @return true if sent immediately, false if queued or dropped - */ - public synchronized boolean sendBinary(ByteString data, Consumer onSent) { - WebSocket ws = webSocket; - if (ws != null) { - boolean sent = ws.send(data); - if (sent && onSent != null) { - onSent.accept(ws); - } - if (!sent && binaryMessageQueue.size() < maxEnqueuedMessages) { - binaryMessageQueue.offer(data); - return false; - } - return sent; - } else { - if (binaryMessageQueue.size() < maxEnqueuedMessages) { - binaryMessageQueue.offer(data); - return false; - } - return false; - } - } - - /** - * Gets the current WebSocket instance. - * Thread-safe method to access the WebSocket connection. - * @return the WebSocket or null if not connected - */ - public WebSocket getWebSocket() { - return webSocket; - } - - /** - * @hidden - */ - @Override - public void onOpen(WebSocket webSocket, Response response) { - this.webSocket = webSocket; - connectionEstablishedTime = System.currentTimeMillis(); - retryCount.set(0); - flushMessageQueue(); - onWebSocketOpen(webSocket, response); - } - - @Override - public void onMessage(WebSocket webSocket, String text) { - onWebSocketMessage(webSocket, text); - } - - @Override - public void onMessage(WebSocket webSocket, ByteString bytes) { - onWebSocketBinaryMessage(webSocket, bytes); - } - - /** - * @hidden - */ - @Override - public void onFailure(WebSocket webSocket, Throwable t, Response response) { - this.webSocket = null; - long uptime = 0L; - if (connectionEstablishedTime > 0) { - uptime = System.currentTimeMillis() - connectionEstablishedTime; - if (uptime >= 5000) { - retryCount.set(0); - } - } - connectionEstablishedTime = 0L; - Throwable enhancedError = t; - if (t != null) { - String errorContext = "WebSocket connection failed"; - if (uptime > 0) { - errorContext += " after " + (uptime / 1000) + " seconds"; - } - if (response != null) { - errorContext += " with HTTP " + response.code() + " " + response.message(); - } - enhancedError = - new RuntimeException(errorContext + ": " + t.getClass().getSimpleName() + ": " + t.getMessage()); - enhancedError.initCause(t); - } - onWebSocketFailure(webSocket, enhancedError, response); - if (shouldReconnect.get()) { - scheduleReconnect(); - } - } - - /** - * Acknowledges a peer-initiated close so OkHttp can complete the close handshake and - * invoke {@link #onClosed(WebSocket, int, String)}. - * - * Code 1005 is a local "no status received" sentinel reported when the peer sent an empty - * close frame. It is not a valid code to put on the wire, so it is acknowledged with - * the normal closure code 1000 instead. - * - * @hidden - */ - @Override - public void onClosing(WebSocket webSocket, int code, String reason) { - webSocket.close(code == 1005 ? 1000 : code, reason); - } - - /** - * @hidden - */ - @Override - public void onClosed(WebSocket webSocket, int code, String reason) { - this.webSocket = null; - if (connectionEstablishedTime > 0) { - long uptime = System.currentTimeMillis() - connectionEstablishedTime; - if (uptime >= 5000) { - retryCount.set(0); - } - } - connectionEstablishedTime = 0L; - onWebSocketClosed(webSocket, code, reason); - if (shouldReconnect.get() && shouldReconnectAfterClose(webSocket, code, reason)) { - scheduleReconnect(); - } - } - - /** - * Decides whether a completed close handshake should trigger a reconnect. - * - * Only consulted when reconnection has not been disabled via {@link #disconnect()}. - * The default treats normal closure (1000) as terminal and reconnects on any - * other code. Subclasses can override this when protocol context establishes that a - * particular close is terminal, for example a no-status close following a message that - * ends the stream. - * - * @param webSocket The WebSocket that was closed - * @param code The close status code reported by OkHttp - * @param reason The close reason sent by the peer, or an empty string - * @return true to schedule a reconnect, false to stay disconnected - */ - protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, String reason) { - return code != 1000; - } - - /** - * Calculates the next reconnection delay using exponential backoff. - * - * Uses 0-based retry count where: - * - 0 = initial connection (not used by this method) - * - 1 = first retry (returns minReconnectionDelayMs) - * - 2+ = exponential backoff up to maxReconnectionDelayMs - */ - private long getNextDelay() { - ReconnectOptions options = this.activeOptions; - if (retryCount.get() == 1) { - return options.minReconnectionDelayMs; - } - long delay = (long) - (options.minReconnectionDelayMs * Math.pow(options.reconnectionDelayGrowFactor, retryCount.get() - 1)); - return Math.min(delay, options.maxReconnectionDelayMs); - } - - /** - * Schedules a reconnection attempt with appropriate delay. - * Increments retry count and uses exponential backoff. - */ - private void scheduleReconnect() { - retryCount.incrementAndGet(); - long delay = getNextDelay(); - reconnectExecutor.schedule(this::connect, delay, MILLISECONDS); - } - - /** - * Sends all queued messages after reconnection. - * - * Thread-safe: Synchronized to prevent race conditions with send() method. - * - * Algorithm: - * 1. Drains queue into temporary list to avoid holding lock during sends - * 2. Attempts to send each message in order - * 3. If any send fails, re-queues that message and all subsequent messages - * 4. Preserves message ordering during re-queueing - * 5. Repeats for binary message queue - */ - private synchronized void flushMessageQueue() { - WebSocket ws = webSocket; - if (ws != null) { - ArrayList tempQueue = new ArrayList<>(); - String message; - while ((message = messageQueue.poll()) != null) { - tempQueue.add(message); - } - for (int i = 0; i < tempQueue.size(); i++) { - if (!ws.send(tempQueue.get(i))) { - for (int j = i; j < tempQueue.size(); j++) { - messageQueue.offer(tempQueue.get(j)); - } - break; - } - } - ArrayList tempBinaryQueue = new ArrayList<>(); - ByteString binaryMsg; - while ((binaryMsg = binaryMessageQueue.poll()) != null) { - tempBinaryQueue.add(binaryMsg); - } - for (int i = 0; i < tempBinaryQueue.size(); i++) { - if (!ws.send(tempBinaryQueue.get(i))) { - for (int j = i; j < tempBinaryQueue.size(); j++) { - binaryMessageQueue.offer(tempBinaryQueue.get(j)); - } - break; - } - } - } - } - - protected abstract void onWebSocketOpen(WebSocket webSocket, Response response); - - protected abstract void onWebSocketMessage(WebSocket webSocket, String text); - - protected abstract void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes); - - protected abstract void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response); - - protected abstract void onWebSocketClosed(WebSocket webSocket, int code, String reason); - - /** - * Configuration options for automatic reconnection. - */ - public static final class ReconnectOptions { - public final long minReconnectionDelayMs; - - public final long maxReconnectionDelayMs; - - public final double reconnectionDelayGrowFactor; - - public final int maxRetries; - - public final int maxEnqueuedMessages; - - public final long connectionTimeoutMs; - - private ReconnectOptions(Builder builder) { - this.minReconnectionDelayMs = builder.minReconnectionDelayMs; - this.maxReconnectionDelayMs = builder.maxReconnectionDelayMs; - this.reconnectionDelayGrowFactor = builder.reconnectionDelayGrowFactor; - this.maxRetries = builder.maxRetries; - this.maxEnqueuedMessages = builder.maxEnqueuedMessages; - this.connectionTimeoutMs = builder.connectionTimeoutMs; - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - private long minReconnectionDelayMs; - - private long maxReconnectionDelayMs; - - private double reconnectionDelayGrowFactor; - - private int maxRetries; - - private int maxEnqueuedMessages; - - private long connectionTimeoutMs; - - public Builder() { - this.minReconnectionDelayMs = 1000; - this.maxReconnectionDelayMs = 10000; - this.reconnectionDelayGrowFactor = 1.3; - this.maxRetries = 2147483647; - this.maxEnqueuedMessages = 1000; - this.connectionTimeoutMs = 4000; - } - - public Builder minReconnectionDelayMs(long minReconnectionDelayMs) { - this.minReconnectionDelayMs = minReconnectionDelayMs; - return this; - } - - public Builder maxReconnectionDelayMs(long maxReconnectionDelayMs) { - this.maxReconnectionDelayMs = maxReconnectionDelayMs; - return this; - } - - public Builder reconnectionDelayGrowFactor(double reconnectionDelayGrowFactor) { - this.reconnectionDelayGrowFactor = reconnectionDelayGrowFactor; - return this; - } - - public Builder maxRetries(int maxRetries) { - this.maxRetries = maxRetries; - return this; - } - - public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { - this.maxEnqueuedMessages = maxEnqueuedMessages; - return this; - } - - /** - * Sets the per-attempt connection timeout in milliseconds. Defaults to {@code 4000}. - */ - public Builder connectionTimeoutMs(long connectionTimeoutMs) { - this.connectionTimeoutMs = connectionTimeoutMs; - return this; - } - - /** - * Builds the ReconnectOptions with validation. - * - * Validates that: - * - All delay values are positive - * - minReconnectionDelayMs <= maxReconnectionDelayMs - * - reconnectionDelayGrowFactor >= 1.0 - * - maxRetries and maxEnqueuedMessages are non-negative - * - * @return The validated ReconnectOptions instance - * @throws IllegalArgumentException if configuration is invalid - */ - public ReconnectOptions build() { - if (minReconnectionDelayMs <= 0) { - throw new IllegalArgumentException("minReconnectionDelayMs must be positive"); - } - if (maxReconnectionDelayMs <= 0) { - throw new IllegalArgumentException("maxReconnectionDelayMs must be positive"); - } - if (minReconnectionDelayMs > maxReconnectionDelayMs) { - throw new IllegalArgumentException("minReconnectionDelayMs (" + minReconnectionDelayMs - + ") must not exceed maxReconnectionDelayMs (" + maxReconnectionDelayMs + ")"); - } - if (reconnectionDelayGrowFactor < 1.0) { - throw new IllegalArgumentException("reconnectionDelayGrowFactor must be >= 1.0"); - } - if (maxRetries < 0) { - throw new IllegalArgumentException("maxRetries must be non-negative"); - } - if (maxEnqueuedMessages < 0) { - throw new IllegalArgumentException("maxEnqueuedMessages must be non-negative"); - } - if (connectionTimeoutMs <= 0) { - throw new IllegalArgumentException("connectionTimeoutMs must be positive"); - } - return new ReconnectOptions(this); - } - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java index 71825ed..5962f4b 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof AgentV1AgentAudioDone; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak deleted file mode 100644 index b6ee900..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1AgentAudioDone.Builder.class) -public final class AgentV1AgentAudioDone { - private final Map additionalProperties; - - private AgentV1AgentAudioDone(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier indicating the agent has finished sending audio - */ - @JsonProperty("type") - public String getType() { - return "AgentAudioDone"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1AgentAudioDone; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1AgentAudioDone other) { - return this; - } - - public AgentV1AgentAudioDone build() { - return new AgentV1AgentAudioDone(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java index 1672d2d..9bd5f76 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof AgentV1ForceEndTurn; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak deleted file mode 100644 index 11d0d8d..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1ForceEndTurn.Builder.class) -public final class AgentV1ForceEndTurn { - private final Map additionalProperties; - - private AgentV1ForceEndTurn(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for forcing the end of the current turn - */ - @JsonProperty("type") - public String getType() { - return "ForceEndTurn"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1ForceEndTurn; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1ForceEndTurn other) { - return this; - } - - public AgentV1ForceEndTurn build() { - return new AgentV1ForceEndTurn(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java index 7842266..f82f59e 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof AgentV1KeepAlive; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak deleted file mode 100644 index 588aad1..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1KeepAlive.Builder.class) -public final class AgentV1KeepAlive { - private final Map additionalProperties; - - private AgentV1KeepAlive(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier - */ - @JsonProperty("type") - public String getType() { - return "KeepAlive"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1KeepAlive; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1KeepAlive other) { - return this; - } - - public AgentV1KeepAlive build() { - return new AgentV1KeepAlive(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java index 65878c9..43b8628 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof AgentV1ListenUpdated; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak deleted file mode 100644 index 3de75ec..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1ListenUpdated.Builder.class) -public final class AgentV1ListenUpdated { - private final Map additionalProperties; - - private AgentV1ListenUpdated(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for listen update confirmation - */ - @JsonProperty("type") - public String getType() { - return "ListenUpdated"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1ListenUpdated; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1ListenUpdated other) { - return this; - } - - public AgentV1ListenUpdated build() { - return new AgentV1ListenUpdated(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java index 68d71cc..e6efced 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof AgentV1PromptUpdated; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak deleted file mode 100644 index 2b2f794..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1PromptUpdated.Builder.class) -public final class AgentV1PromptUpdated { - private final Map additionalProperties; - - private AgentV1PromptUpdated(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for prompt update confirmation - */ - @JsonProperty("type") - public String getType() { - return "PromptUpdated"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1PromptUpdated; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1PromptUpdated other) { - return this; - } - - public AgentV1PromptUpdated build() { - return new AgentV1PromptUpdated(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java index eea1585..393f1f9 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java @@ -99,7 +99,7 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak deleted file mode 100644 index 1c21ce7..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak +++ /dev/null @@ -1,226 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.types.DeepgramListenProviderV1; -import com.deepgram.types.DeepgramListenProviderV2; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Objects; -import java.util.Optional; - -public final class AgentV1SettingsAgentContextListenProvider { - private final Value value; - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - private AgentV1SettingsAgentContextListenProvider(Value value) { - this.value = value; - } - - public T visit(Visitor visitor) { - return value.visit(visitor); - } - - public static AgentV1SettingsAgentContextListenProvider v1(DeepgramListenProviderV1 value) { - return new AgentV1SettingsAgentContextListenProvider(new V1Value(value)); - } - - public static AgentV1SettingsAgentContextListenProvider v2(DeepgramListenProviderV2 value) { - return new AgentV1SettingsAgentContextListenProvider(new V2Value(value)); - } - - public boolean isV1() { - return value instanceof V1Value; - } - - public boolean isV2() { - return value instanceof V2Value; - } - - public boolean _isUnknown() { - return value instanceof _UnknownValue; - } - - public Optional getV1() { - if (isV1()) { - return Optional.of(((V1Value) value).value); - } - return Optional.empty(); - } - - public Optional getV2() { - if (isV2()) { - return Optional.of(((V2Value) value).value); - } - return Optional.empty(); - } - - public Optional _getUnknown() { - if (_isUnknown()) { - return Optional.of(((_UnknownValue) value).value); - } - return Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SettingsAgentContextListenProvider - && value.equals(((AgentV1SettingsAgentContextListenProvider) other).value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return value.toString(); - } - - @JsonValue - private Value getValue() { - return this.value; - } - - public interface Visitor { - T visitV1(DeepgramListenProviderV1 v1); - - T visitV2(DeepgramListenProviderV2 v2); - - T _visitUnknown(Object unknownType); - } - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) - @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) - @JsonIgnoreProperties(ignoreUnknown = true) - private interface Value { - T visit(Visitor visitor); - } - - @JsonTypeName("v1") - @JsonIgnoreProperties("version") - private static final class V1Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV1 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V1Value() {} - - private V1Value(DeepgramListenProviderV1 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV1(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V1Value && equalTo((V1Value) other); - } - - private boolean equalTo(V1Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentContextListenProvider{" + "value: " + value + "}"; - } - } - - @JsonTypeName("v2") - @JsonIgnoreProperties("version") - private static final class V2Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV2 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V2Value() {} - - private V2Value(DeepgramListenProviderV2 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV2(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V2Value && equalTo((V2Value) other); - } - - private boolean equalTo(V2Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentContextListenProvider{" + "value: " + value + "}"; - } - } - - @JsonIgnoreProperties("version") - private static final class _UnknownValue implements Value { - private String type; - - @JsonValue - private Object value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private _UnknownValue(@JsonProperty("value") Object value) {} - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor._visitUnknown(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof _UnknownValue && equalTo((_UnknownValue) other); - } - - private boolean equalTo(_UnknownValue other) { - return type.equals(other.type) && value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.type, this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentContextListenProvider{" + "type: " + type + ", value: " + value + "}"; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java index 4309ec0..345ba2f 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java @@ -99,7 +99,7 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak deleted file mode 100644 index 06e47e6..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak +++ /dev/null @@ -1,226 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.types.DeepgramListenProviderV1; -import com.deepgram.types.DeepgramListenProviderV2; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Objects; -import java.util.Optional; - -public final class AgentV1SettingsAgentListenProvider { - private final Value value; - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - private AgentV1SettingsAgentListenProvider(Value value) { - this.value = value; - } - - public T visit(Visitor visitor) { - return value.visit(visitor); - } - - public static AgentV1SettingsAgentListenProvider v1(DeepgramListenProviderV1 value) { - return new AgentV1SettingsAgentListenProvider(new V1Value(value)); - } - - public static AgentV1SettingsAgentListenProvider v2(DeepgramListenProviderV2 value) { - return new AgentV1SettingsAgentListenProvider(new V2Value(value)); - } - - public boolean isV1() { - return value instanceof V1Value; - } - - public boolean isV2() { - return value instanceof V2Value; - } - - public boolean _isUnknown() { - return value instanceof _UnknownValue; - } - - public Optional getV1() { - if (isV1()) { - return Optional.of(((V1Value) value).value); - } - return Optional.empty(); - } - - public Optional getV2() { - if (isV2()) { - return Optional.of(((V2Value) value).value); - } - return Optional.empty(); - } - - public Optional _getUnknown() { - if (_isUnknown()) { - return Optional.of(((_UnknownValue) value).value); - } - return Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SettingsAgentListenProvider - && value.equals(((AgentV1SettingsAgentListenProvider) other).value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return value.toString(); - } - - @JsonValue - private Value getValue() { - return this.value; - } - - public interface Visitor { - T visitV1(DeepgramListenProviderV1 v1); - - T visitV2(DeepgramListenProviderV2 v2); - - T _visitUnknown(Object unknownType); - } - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) - @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) - @JsonIgnoreProperties(ignoreUnknown = true) - private interface Value { - T visit(Visitor visitor); - } - - @JsonTypeName("v1") - @JsonIgnoreProperties("version") - private static final class V1Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV1 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V1Value() {} - - private V1Value(DeepgramListenProviderV1 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV1(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V1Value && equalTo((V1Value) other); - } - - private boolean equalTo(V1Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentListenProvider{" + "value: " + value + "}"; - } - } - - @JsonTypeName("v2") - @JsonIgnoreProperties("version") - private static final class V2Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV2 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V2Value() {} - - private V2Value(DeepgramListenProviderV2 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV2(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V2Value && equalTo((V2Value) other); - } - - private boolean equalTo(V2Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentListenProvider{" + "value: " + value + "}"; - } - } - - @JsonIgnoreProperties("version") - private static final class _UnknownValue implements Value { - private String type; - - @JsonValue - private Object value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private _UnknownValue(@JsonProperty("value") Object value) {} - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor._visitUnknown(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof _UnknownValue && equalTo((_UnknownValue) other); - } - - private boolean equalTo(_UnknownValue other) { - return type.equals(other.type) && value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.type, this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentListenProvider{" + "type: " + type + ", value: " + value + "}"; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java index 0d60b72..ad1fdb2 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof AgentV1SettingsApplied; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak deleted file mode 100644 index 443c214..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1SettingsApplied.Builder.class) -public final class AgentV1SettingsApplied { - private final Map additionalProperties; - - private AgentV1SettingsApplied(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for settings applied confirmation - */ - @JsonProperty("type") - public String getType() { - return "SettingsApplied"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SettingsApplied; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1SettingsApplied other) { - return this; - } - - public AgentV1SettingsApplied build() { - return new AgentV1SettingsApplied(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java index 747aa9d..8b134b5 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof AgentV1SpeakUpdated; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak deleted file mode 100644 index 87c8d7d..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1SpeakUpdated.Builder.class) -public final class AgentV1SpeakUpdated { - private final Map additionalProperties; - - private AgentV1SpeakUpdated(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for speak update confirmation - */ - @JsonProperty("type") - public String getType() { - return "SpeakUpdated"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SpeakUpdated; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1SpeakUpdated other) { - return this; - } - - public AgentV1SpeakUpdated build() { - return new AgentV1SpeakUpdated(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java index 5668a71..2d67cad 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof AgentV1ThinkUpdated; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak deleted file mode 100644 index 4b6f623..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1ThinkUpdated.Builder.class) -public final class AgentV1ThinkUpdated { - private final Map additionalProperties; - - private AgentV1ThinkUpdated(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for think update confirmation - */ - @JsonProperty("type") - public String getType() { - return "ThinkUpdated"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1ThinkUpdated; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1ThinkUpdated other) { - return this; - } - - public AgentV1ThinkUpdated build() { - return new AgentV1ThinkUpdated(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java index e693a04..e739aec 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java @@ -99,7 +99,7 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak deleted file mode 100644 index d0390e2..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak +++ /dev/null @@ -1,226 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.types.DeepgramListenProviderV1; -import com.deepgram.types.DeepgramListenProviderV2; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Objects; -import java.util.Optional; - -public final class AgentV1UpdateListenListenProvider { - private final Value value; - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - private AgentV1UpdateListenListenProvider(Value value) { - this.value = value; - } - - public T visit(Visitor visitor) { - return value.visit(visitor); - } - - public static AgentV1UpdateListenListenProvider v1(DeepgramListenProviderV1 value) { - return new AgentV1UpdateListenListenProvider(new V1Value(value)); - } - - public static AgentV1UpdateListenListenProvider v2(DeepgramListenProviderV2 value) { - return new AgentV1UpdateListenListenProvider(new V2Value(value)); - } - - public boolean isV1() { - return value instanceof V1Value; - } - - public boolean isV2() { - return value instanceof V2Value; - } - - public boolean _isUnknown() { - return value instanceof _UnknownValue; - } - - public Optional getV1() { - if (isV1()) { - return Optional.of(((V1Value) value).value); - } - return Optional.empty(); - } - - public Optional getV2() { - if (isV2()) { - return Optional.of(((V2Value) value).value); - } - return Optional.empty(); - } - - public Optional _getUnknown() { - if (_isUnknown()) { - return Optional.of(((_UnknownValue) value).value); - } - return Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1UpdateListenListenProvider - && value.equals(((AgentV1UpdateListenListenProvider) other).value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return value.toString(); - } - - @JsonValue - private Value getValue() { - return this.value; - } - - public interface Visitor { - T visitV1(DeepgramListenProviderV1 v1); - - T visitV2(DeepgramListenProviderV2 v2); - - T _visitUnknown(Object unknownType); - } - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) - @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) - @JsonIgnoreProperties(ignoreUnknown = true) - private interface Value { - T visit(Visitor visitor); - } - - @JsonTypeName("v1") - @JsonIgnoreProperties("version") - private static final class V1Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV1 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V1Value() {} - - private V1Value(DeepgramListenProviderV1 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV1(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V1Value && equalTo((V1Value) other); - } - - private boolean equalTo(V1Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1UpdateListenListenProvider{" + "value: " + value + "}"; - } - } - - @JsonTypeName("v2") - @JsonIgnoreProperties("version") - private static final class V2Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV2 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V2Value() {} - - private V2Value(DeepgramListenProviderV2 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV2(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V2Value && equalTo((V2Value) other); - } - - private boolean equalTo(V2Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1UpdateListenListenProvider{" + "value: " + value + "}"; - } - } - - @JsonIgnoreProperties("version") - private static final class _UnknownValue implements Value { - private String type; - - @JsonValue - private Object value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private _UnknownValue(@JsonProperty("value") Object value) {} - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor._visitUnknown(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof _UnknownValue && equalTo((_UnknownValue) other); - } - - private boolean equalTo(_UnknownValue other) { - return type.equals(other.type) && value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.type, this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1UpdateListenListenProvider{" + "type: " + type + ", value: " + value + "}"; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java index edb80f8..3556d31 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof AgentV1UserStartedSpeaking; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak deleted file mode 100644 index 36e8a9d..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1UserStartedSpeaking.Builder.class) -public final class AgentV1UserStartedSpeaking { - private final Map additionalProperties; - - private AgentV1UserStartedSpeaking(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier indicating that the user has begun speaking - */ - @JsonProperty("type") - public String getType() { - return "UserStartedSpeaking"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1UserStartedSpeaking; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1UserStartedSpeaking other) { - return this; - } - - public AgentV1UserStartedSpeaking build() { - return new AgentV1UserStartedSpeaking(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak deleted file mode 100644 index d8305b1..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java.bak +++ /dev/null @@ -1,868 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.websocket; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ClientOptions; -import com.deepgram.core.DisconnectReason; -import com.deepgram.core.ObjectMappers; -import com.deepgram.core.ReconnectingWebSocketListener; -import com.deepgram.core.RequestOptions; -import com.deepgram.core.WebSocketReadyState; -import com.deepgram.resources.agent.v1.types.AgentV1AgentAudioDone; -import com.deepgram.resources.agent.v1.types.AgentV1AgentStartedSpeaking; -import com.deepgram.resources.agent.v1.types.AgentV1AgentThinking; -import com.deepgram.resources.agent.v1.types.AgentV1ConversationText; -import com.deepgram.resources.agent.v1.types.AgentV1Error; -import com.deepgram.resources.agent.v1.types.AgentV1ForceEndTurn; -import com.deepgram.resources.agent.v1.types.AgentV1FunctionCallCancelled; -import com.deepgram.resources.agent.v1.types.AgentV1FunctionCallRequest; -import com.deepgram.resources.agent.v1.types.AgentV1History; -import com.deepgram.resources.agent.v1.types.AgentV1InjectAgentMessage; -import com.deepgram.resources.agent.v1.types.AgentV1InjectUserMessage; -import com.deepgram.resources.agent.v1.types.AgentV1InjectionRefused; -import com.deepgram.resources.agent.v1.types.AgentV1KeepAlive; -import com.deepgram.resources.agent.v1.types.AgentV1LatencyReport; -import com.deepgram.resources.agent.v1.types.AgentV1ListenUpdated; -import com.deepgram.resources.agent.v1.types.AgentV1PromptUpdated; -import com.deepgram.resources.agent.v1.types.AgentV1ReceiveFunctionCallResponse; -import com.deepgram.resources.agent.v1.types.AgentV1SendFunctionCallResponse; -import com.deepgram.resources.agent.v1.types.AgentV1Settings; -import com.deepgram.resources.agent.v1.types.AgentV1SettingsApplied; -import com.deepgram.resources.agent.v1.types.AgentV1SpeakUpdated; -import com.deepgram.resources.agent.v1.types.AgentV1ThinkUpdated; -import com.deepgram.resources.agent.v1.types.AgentV1UpdateListen; -import com.deepgram.resources.agent.v1.types.AgentV1UpdatePrompt; -import com.deepgram.resources.agent.v1.types.AgentV1UpdateSpeak; -import com.deepgram.resources.agent.v1.types.AgentV1UpdateThink; -import com.deepgram.resources.agent.v1.types.AgentV1UserStartedSpeaking; -import com.deepgram.resources.agent.v1.types.AgentV1Warning; -import com.deepgram.resources.agent.v1.types.AgentV1Welcome; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Consumer; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okio.ByteString; - -/** - * WebSocket client for the v1 channel. - * Provides real-time bidirectional communication with strongly-typed messages. - */ -public class V1WebSocketClient implements AutoCloseable { - protected final ClientOptions clientOptions; - - private final ObjectMapper objectMapper; - - private final OkHttpClient okHttpClient; - - private ScheduledExecutorService timeoutExecutor; - - private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; - - private volatile Runnable onConnectedHandler; - - private volatile Consumer onDisconnectedHandler; - - private volatile Consumer onErrorHandler; - - private volatile Consumer onMessageHandler; - - private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; - - private CompletableFuture connectionFuture; - - private ReconnectingWebSocketListener reconnectingListener; - - private volatile Consumer listenUpdatedHandler; - - private volatile Consumer thinkUpdatedHandler; - - private volatile Consumer onFunctionCallResponseHandler; - - private volatile Consumer promptUpdatedHandler; - - private volatile Consumer speakUpdatedHandler; - - private volatile Consumer injectionRefusedHandler; - - private volatile Consumer welcomeHandler; - - private volatile Consumer settingsAppliedHandler; - - private volatile Consumer conversationTextHandler; - - private volatile Consumer userStartedSpeakingHandler; - - private volatile Consumer agentThinkingHandler; - - private volatile Consumer latencyReportHandler; - - private volatile Consumer functionCallRequestHandler; - - private volatile Consumer functionCallCancelledHandler; - - private volatile Consumer agentStartedSpeakingHandler; - - private volatile Consumer agentAudioDoneHandler; - - private volatile Consumer errorHandler; - - private volatile Consumer warningHandler; - - private volatile Consumer agentV1HistoryHandler; - - private volatile Consumer agentV1AudioHandler; - - /** - * Creates a new async WebSocket client for the v1 channel. - */ - public V1WebSocketClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.objectMapper = ObjectMappers.JSON_MAPPER; - this.okHttpClient = clientOptions.httpClient(); - } - - /** - * Establishes the WebSocket connection asynchronously with automatic reconnection. - * @return a CompletableFuture that completes when the connection is established - */ - public CompletableFuture connect() { - connectionFuture = new CompletableFuture<>(); - String baseUrl = clientOptions.environment().getAgentURL(); - String fullPath = "/v1/agent/converse"; - if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { - fullPath = fullPath.substring(1); - } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { - fullPath = "/" + fullPath; - } - // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing - if (baseUrl.startsWith("wss://")) { - baseUrl = "https://" + baseUrl.substring(6); - } else if (baseUrl.startsWith("ws://")) { - baseUrl = "http://" + baseUrl.substring(5); - } - HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); - if (parsedUrl == null) { - throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); - } - HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); - Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); - final Request request = requestBuilder.build(); - this.readyState = WebSocketReadyState.CONNECTING; - ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null - ? this.reconnectOptions - : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); - this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { - @Override - protected void onWebSocketOpen(WebSocket webSocket, Response response) { - readyState = WebSocketReadyState.OPEN; - if (onConnectedHandler != null) { - onConnectedHandler.run(); - } - connectionFuture.complete(null); - } - - @Override - protected void onWebSocketMessage(WebSocket webSocket, String text) { - handleIncomingMessage(text); - } - - @Override - protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { - if (agentV1AudioHandler != null) { - agentV1AudioHandler.accept(bytes); - } - } - - @Override - protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { - readyState = WebSocketReadyState.CLOSED; - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException(t)); - } - connectionFuture.completeExceptionally(t); - } - - @Override - protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { - readyState = WebSocketReadyState.CLOSED; - if (onDisconnectedHandler != null) { - onDisconnectedHandler.accept(new DisconnectReason(code, reason)); - } - } - }; - reconnectingListener.connect(); - return connectionFuture; - } - - /** - * Disconnects the WebSocket connection and releases resources. - */ - public void disconnect() { - if (reconnectingListener != null) { - reconnectingListener.disconnect(); - } - if (timeoutExecutor != null) { - timeoutExecutor.shutdownNow(); - timeoutExecutor = null; - } - } - - /** - * Gets the current state of the WebSocket connection. - * - * This provides the actual connection state, similar to the W3C WebSocket API. - * - * @return the current WebSocket ready state - */ - public WebSocketReadyState getReadyState() { - return readyState; - } - - /** - * Sends an AgentV1Settings message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendSettings(AgentV1Settings message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1UpdateListen message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendUpdateListen(AgentV1UpdateListen message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1UpdateThink message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendUpdateThink(AgentV1UpdateThink message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1UpdateSpeak message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendUpdateSpeak(AgentV1UpdateSpeak message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1InjectUserMessage message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendInjectUserMessage(AgentV1InjectUserMessage message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1InjectAgentMessage message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendInjectAgentMessage(AgentV1InjectAgentMessage message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1SendFunctionCallResponse message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendFunctionCallResponse(AgentV1SendFunctionCallResponse message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1KeepAlive message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendKeepAlive(AgentV1KeepAlive message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1UpdatePrompt message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendUpdatePrompt(AgentV1UpdatePrompt message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1ForceEndTurn message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendForceEndTurn(AgentV1ForceEndTurn message) { - return sendMessage(message); - } - - /** - * Sends an AgentV1Media message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendMedia(ByteString message) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - // Use reconnecting listener's sendBinary method which handles queuing - reconnectingListener.sendBinary(message); - future.complete(null); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); - } - return future; - } - - /** - * Registers a handler for AgentV1ListenUpdated messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onListenUpdated(Consumer handler) { - this.listenUpdatedHandler = handler; - } - - /** - * Registers a handler for AgentV1ThinkUpdated messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onThinkUpdated(Consumer handler) { - this.thinkUpdatedHandler = handler; - } - - /** - * Registers a handler for AgentV1ReceiveFunctionCallResponse messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onFunctionCallResponse(Consumer handler) { - this.onFunctionCallResponseHandler = handler; - } - - /** - * Registers a handler for AgentV1PromptUpdated messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onPromptUpdated(Consumer handler) { - this.promptUpdatedHandler = handler; - } - - /** - * Registers a handler for AgentV1SpeakUpdated messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeakUpdated(Consumer handler) { - this.speakUpdatedHandler = handler; - } - - /** - * Registers a handler for AgentV1InjectionRefused messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onInjectionRefused(Consumer handler) { - this.injectionRefusedHandler = handler; - } - - /** - * Registers a handler for AgentV1Welcome messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onWelcome(Consumer handler) { - this.welcomeHandler = handler; - } - - /** - * Registers a handler for AgentV1SettingsApplied messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSettingsApplied(Consumer handler) { - this.settingsAppliedHandler = handler; - } - - /** - * Registers a handler for AgentV1ConversationText messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConversationText(Consumer handler) { - this.conversationTextHandler = handler; - } - - /** - * Registers a handler for AgentV1UserStartedSpeaking messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onUserStartedSpeaking(Consumer handler) { - this.userStartedSpeakingHandler = handler; - } - - /** - * Registers a handler for AgentV1AgentThinking messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onAgentThinking(Consumer handler) { - this.agentThinkingHandler = handler; - } - - /** - * Registers a handler for AgentV1LatencyReport messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onLatencyReport(Consumer handler) { - this.latencyReportHandler = handler; - } - - /** - * Registers a handler for AgentV1FunctionCallRequest messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onFunctionCallRequest(Consumer handler) { - this.functionCallRequestHandler = handler; - } - - /** - * Registers a handler for AgentV1FunctionCallCancelled messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onFunctionCallCancelled(Consumer handler) { - this.functionCallCancelledHandler = handler; - } - - /** - * Registers a handler for AgentV1AgentStartedSpeaking messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onAgentStartedSpeaking(Consumer handler) { - this.agentStartedSpeakingHandler = handler; - } - - /** - * Registers a handler for AgentV1AgentAudioDone messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onAgentAudioDone(Consumer handler) { - this.agentAudioDoneHandler = handler; - } - - /** - * Registers a handler for AgentV1Error messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onErrorMessage(Consumer handler) { - this.errorHandler = handler; - } - - /** - * Registers a handler for AgentV1Warning messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onWarning(Consumer handler) { - this.warningHandler = handler; - } - - /** - * Registers a handler for AgentV1History messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onAgentV1History(Consumer handler) { - this.agentV1HistoryHandler = handler; - } - - /** - * Registers a handler for AgentV1Audio messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onAgentV1Audio(Consumer handler) { - this.agentV1AudioHandler = handler; - } - - /** - * Registers a handler called when the connection is established. - * @param handler the handler to invoke when connected - */ - public void onConnected(Runnable handler) { - this.onConnectedHandler = handler; - } - - /** - * Registers a handler called when the connection is closed. - * @param handler the handler to invoke when disconnected - */ - public void onDisconnected(Consumer handler) { - this.onDisconnectedHandler = handler; - } - - /** - * Registers a handler called when an error occurs. - * @param handler the handler to invoke on error - */ - public void onError(Consumer handler) { - this.onErrorHandler = handler; - } - - /** - * Registers a handler called for every incoming text message. - * The handler receives the raw JSON string before type-specific dispatch. - * @param handler the handler to invoke with the raw message JSON - */ - public void onMessage(Consumer handler) { - this.onMessageHandler = handler; - } - - /** - * Configures reconnection behavior. Must be called before {@link #connect}. - * - * @param options the reconnection options (backoff, retries, queue size) - */ - public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { - this.reconnectOptions = options; - } - - /** - * Closes this WebSocket client, releasing all resources. - * Equivalent to calling {@link #disconnect()}. - */ - @Override - public void close() { - disconnect(); - } - - /** - * Ensures the WebSocket is connected and ready to send messages. - * @throws IllegalStateException if the socket is not connected or not open - */ - private void assertSocketIsOpen() { - if (reconnectingListener.getWebSocket() == null) { - throw new IllegalStateException("WebSocket is not connected. Call connect() first."); - } - if (readyState != WebSocketReadyState.OPEN) { - throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); - } - } - - private CompletableFuture sendMessage(Object body) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - String json = objectMapper.writeValueAsString(body); - // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json); - future.complete(null); - } catch (IllegalStateException e) { - future.completeExceptionally(e); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send message", e)); - } - return future; - } - - private void handleIncomingMessage(String json) { - try { - if (onMessageHandler != null) { - onMessageHandler.accept(json); - } - JsonNode node = objectMapper.readTree(json); - if (node == null || node.isNull()) { - throw new IllegalArgumentException("Received null or invalid JSON message"); - } - if (node.has("total_latency") - && node.has("tts_latency") - && node.has("ttt_latency") - && "AgentStartedSpeaking".equals(node.path("type").asText())) { - AgentV1AgentStartedSpeaking agentStartedSpeakingHandlerEvent = null; - try { - agentStartedSpeakingHandlerEvent = - objectMapper.treeToValue(node, AgentV1AgentStartedSpeaking.class); - } catch (Exception e) { - } - if (agentStartedSpeakingHandlerEvent != null) { - if (agentStartedSpeakingHandler != null) { - agentStartedSpeakingHandler.accept(agentStartedSpeakingHandlerEvent); - } - return; - } - } - if (node.has("name") - && node.has("content") - && "FunctionCallResponse".equals(node.path("type").asText())) { - AgentV1ReceiveFunctionCallResponse onFunctionCallResponseHandlerEvent = null; - try { - onFunctionCallResponseHandlerEvent = - objectMapper.treeToValue(node, AgentV1ReceiveFunctionCallResponse.class); - } catch (Exception e) { - } - if (onFunctionCallResponseHandlerEvent != null) { - if (onFunctionCallResponseHandler != null) { - onFunctionCallResponseHandler.accept(onFunctionCallResponseHandlerEvent); - } - return; - } - } - if (node.has("role") - && node.has("content") - && "ConversationText".equals(node.path("type").asText())) { - AgentV1ConversationText conversationTextHandlerEvent = null; - try { - conversationTextHandlerEvent = objectMapper.treeToValue(node, AgentV1ConversationText.class); - } catch (Exception e) { - } - if (conversationTextHandlerEvent != null) { - if (conversationTextHandler != null) { - conversationTextHandler.accept(conversationTextHandlerEvent); - } - return; - } - } - if (node.has("description") - && node.has("code") - && "Error".equals(node.path("type").asText())) { - AgentV1Error errorHandlerEvent = null; - try { - errorHandlerEvent = objectMapper.treeToValue(node, AgentV1Error.class); - } catch (Exception e) { - } - if (errorHandlerEvent != null) { - if (errorHandler != null) { - errorHandler.accept(errorHandlerEvent); - } - return; - } - } - if (node.has("description") - && node.has("code") - && "Warning".equals(node.path("type").asText())) { - AgentV1Warning warningHandlerEvent = null; - try { - warningHandlerEvent = objectMapper.treeToValue(node, AgentV1Warning.class); - } catch (Exception e) { - } - if (warningHandlerEvent != null) { - if (warningHandler != null) { - warningHandler.accept(warningHandlerEvent); - } - return; - } - } - if (node.has("message") - && "InjectionRefused".equals(node.path("type").asText())) { - AgentV1InjectionRefused injectionRefusedHandlerEvent = null; - try { - injectionRefusedHandlerEvent = objectMapper.treeToValue(node, AgentV1InjectionRefused.class); - } catch (Exception e) { - } - if (injectionRefusedHandlerEvent != null) { - if (injectionRefusedHandler != null) { - injectionRefusedHandler.accept(injectionRefusedHandlerEvent); - } - return; - } - } - if (node.has("request_id") && "Welcome".equals(node.path("type").asText())) { - AgentV1Welcome welcomeHandlerEvent = null; - try { - welcomeHandlerEvent = objectMapper.treeToValue(node, AgentV1Welcome.class); - } catch (Exception e) { - } - if (welcomeHandlerEvent != null) { - if (welcomeHandler != null) { - welcomeHandler.accept(welcomeHandlerEvent); - } - return; - } - } - if (node.has("content") && "AgentThinking".equals(node.path("type").asText())) { - AgentV1AgentThinking agentThinkingHandlerEvent = null; - try { - agentThinkingHandlerEvent = objectMapper.treeToValue(node, AgentV1AgentThinking.class); - } catch (Exception e) { - } - if (agentThinkingHandlerEvent != null) { - if (agentThinkingHandler != null) { - agentThinkingHandler.accept(agentThinkingHandlerEvent); - } - return; - } - } - if (node.has("functions") - && "FunctionCallRequest".equals(node.path("type").asText())) { - AgentV1FunctionCallRequest functionCallRequestHandlerEvent = null; - try { - functionCallRequestHandlerEvent = objectMapper.treeToValue(node, AgentV1FunctionCallRequest.class); - } catch (Exception e) { - } - if (functionCallRequestHandlerEvent != null) { - if (functionCallRequestHandler != null) { - functionCallRequestHandler.accept(functionCallRequestHandlerEvent); - } - return; - } - } - if (node.has("functions") - && "FunctionCallCancelled".equals(node.path("type").asText())) { - AgentV1FunctionCallCancelled functionCallCancelledHandlerEvent = null; - try { - functionCallCancelledHandlerEvent = - objectMapper.treeToValue(node, AgentV1FunctionCallCancelled.class); - } catch (Exception e) { - } - if (functionCallCancelledHandlerEvent != null) { - if (functionCallCancelledHandler != null) { - functionCallCancelledHandler.accept(functionCallCancelledHandlerEvent); - } - return; - } - } - if ("ListenUpdated".equals(node.path("type").asText())) { - AgentV1ListenUpdated listenUpdatedHandlerEvent = null; - try { - listenUpdatedHandlerEvent = objectMapper.treeToValue(node, AgentV1ListenUpdated.class); - } catch (Exception e) { - } - if (listenUpdatedHandlerEvent != null) { - if (listenUpdatedHandler != null) { - listenUpdatedHandler.accept(listenUpdatedHandlerEvent); - } - return; - } - } - if ("ThinkUpdated".equals(node.path("type").asText())) { - AgentV1ThinkUpdated thinkUpdatedHandlerEvent = null; - try { - thinkUpdatedHandlerEvent = objectMapper.treeToValue(node, AgentV1ThinkUpdated.class); - } catch (Exception e) { - } - if (thinkUpdatedHandlerEvent != null) { - if (thinkUpdatedHandler != null) { - thinkUpdatedHandler.accept(thinkUpdatedHandlerEvent); - } - return; - } - } - if ("PromptUpdated".equals(node.path("type").asText())) { - AgentV1PromptUpdated promptUpdatedHandlerEvent = null; - try { - promptUpdatedHandlerEvent = objectMapper.treeToValue(node, AgentV1PromptUpdated.class); - } catch (Exception e) { - } - if (promptUpdatedHandlerEvent != null) { - if (promptUpdatedHandler != null) { - promptUpdatedHandler.accept(promptUpdatedHandlerEvent); - } - return; - } - } - if ("SpeakUpdated".equals(node.path("type").asText())) { - AgentV1SpeakUpdated speakUpdatedHandlerEvent = null; - try { - speakUpdatedHandlerEvent = objectMapper.treeToValue(node, AgentV1SpeakUpdated.class); - } catch (Exception e) { - } - if (speakUpdatedHandlerEvent != null) { - if (speakUpdatedHandler != null) { - speakUpdatedHandler.accept(speakUpdatedHandlerEvent); - } - return; - } - } - if ("SettingsApplied".equals(node.path("type").asText())) { - AgentV1SettingsApplied settingsAppliedHandlerEvent = null; - try { - settingsAppliedHandlerEvent = objectMapper.treeToValue(node, AgentV1SettingsApplied.class); - } catch (Exception e) { - } - if (settingsAppliedHandlerEvent != null) { - if (settingsAppliedHandler != null) { - settingsAppliedHandler.accept(settingsAppliedHandlerEvent); - } - return; - } - } - if ("UserStartedSpeaking".equals(node.path("type").asText())) { - AgentV1UserStartedSpeaking userStartedSpeakingHandlerEvent = null; - try { - userStartedSpeakingHandlerEvent = objectMapper.treeToValue(node, AgentV1UserStartedSpeaking.class); - } catch (Exception e) { - } - if (userStartedSpeakingHandlerEvent != null) { - if (userStartedSpeakingHandler != null) { - userStartedSpeakingHandler.accept(userStartedSpeakingHandlerEvent); - } - return; - } - } - if ("LatencyReport".equals(node.path("type").asText())) { - AgentV1LatencyReport latencyReportHandlerEvent = null; - try { - latencyReportHandlerEvent = objectMapper.treeToValue(node, AgentV1LatencyReport.class); - } catch (Exception e) { - } - if (latencyReportHandlerEvent != null) { - if (latencyReportHandler != null) { - latencyReportHandler.accept(latencyReportHandlerEvent); - } - return; - } - } - if ("AgentAudioDone".equals(node.path("type").asText())) { - AgentV1AgentAudioDone agentAudioDoneHandlerEvent = null; - try { - agentAudioDoneHandlerEvent = objectMapper.treeToValue(node, AgentV1AgentAudioDone.class); - } catch (Exception e) { - } - if (agentAudioDoneHandlerEvent != null) { - if (agentAudioDoneHandler != null) { - agentAudioDoneHandler.accept(agentAudioDoneHandlerEvent); - } - return; - } - } - AgentV1History agentV1HistoryHandlerEvent = null; - try { - agentV1HistoryHandlerEvent = objectMapper.treeToValue(node, AgentV1History.class); - } catch (Exception e) { - } - if (agentV1HistoryHandlerEvent != null) { - if (agentV1HistoryHandler != null) { - agentV1HistoryHandler.accept(agentV1HistoryHandlerEvent); - } - return; - } - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException( - "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) - + "... Update your SDK version to support new message types.")); - } - } catch (Exception e) { - if (onErrorHandler != null) { - onErrorHandler.accept(e); - } - } - } -} diff --git a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java index 88c9ebe..278a2c9 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java @@ -6,6 +6,7 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -138,8 +139,7 @@ public CompletableFuture connect(V1ConnectOptions options) { "endpointing", String.valueOf(options.getEndpointing().get())); } if (options.getExtra() != null && options.getExtra().isPresent()) { - urlBuilder.addQueryParameter( - "extra", String.valueOf(options.getExtra().get())); + QueryStringMapper.addQueryParameter(urlBuilder, "extra", options.getExtra().get().get(), true); } if (options.getInterimResults() != null && options.getInterimResults().isPresent()) { urlBuilder.addQueryParameter( @@ -147,12 +147,10 @@ public CompletableFuture connect(V1ConnectOptions options) { String.valueOf(options.getInterimResults().get())); } if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - urlBuilder.addQueryParameter( - "keyterm", String.valueOf(options.getKeyterm().get())); + QueryStringMapper.addQueryParameter(urlBuilder, "keyterm", options.getKeyterm().get().get(), true); } if (options.getKeywords() != null && options.getKeywords().isPresent()) { - urlBuilder.addQueryParameter( - "keywords", String.valueOf(options.getKeywords().get())); + QueryStringMapper.addQueryParameter(urlBuilder, "keywords", options.getKeywords().get().get(), true); } if (options.getLanguage() != null && options.getLanguage().isPresent()) { urlBuilder.addQueryParameter( @@ -185,23 +183,21 @@ public CompletableFuture connect(V1ConnectOptions options) { "redact", String.valueOf(options.getRedact().get())); } if (options.getReplace() != null && options.getReplace().isPresent()) { - urlBuilder.addQueryParameter( - "replace", String.valueOf(options.getReplace().get())); + QueryStringMapper.addQueryParameter(urlBuilder, "replace", options.getReplace().get().get(), true); } if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { urlBuilder.addQueryParameter( "sample_rate", String.valueOf(options.getSampleRate().get())); } if (options.getSearch() != null && options.getSearch().isPresent()) { - urlBuilder.addQueryParameter( - "search", String.valueOf(options.getSearch().get())); + QueryStringMapper.addQueryParameter(urlBuilder, "search", options.getSearch().get().get(), true); } if (options.getSmartFormat() != null && options.getSmartFormat().isPresent()) { urlBuilder.addQueryParameter( "smart_format", String.valueOf(options.getSmartFormat().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); + QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); } if (options.getUtteranceEndMs() != null && options.getUtteranceEndMs().isPresent()) { urlBuilder.addQueryParameter( @@ -216,6 +212,13 @@ public CompletableFuture connect(V1ConnectOptions options) { urlBuilder.addQueryParameter( "version", String.valueOf(options.getVersion().get())); } + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); diff --git a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak deleted file mode 100644 index ffaa903..0000000 --- a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak +++ /dev/null @@ -1,545 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.listen.v1.websocket; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ClientOptions; -import com.deepgram.core.DisconnectReason; -import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; -import com.deepgram.core.ReconnectingWebSocketListener; -import com.deepgram.core.RequestOptions; -import com.deepgram.core.WebSocketReadyState; -import com.deepgram.resources.listen.v1.types.ListenV1CloseStream; -import com.deepgram.resources.listen.v1.types.ListenV1Finalize; -import com.deepgram.resources.listen.v1.types.ListenV1KeepAlive; -import com.deepgram.resources.listen.v1.types.ListenV1Metadata; -import com.deepgram.resources.listen.v1.types.ListenV1Results; -import com.deepgram.resources.listen.v1.types.ListenV1SpeechStarted; -import com.deepgram.resources.listen.v1.types.ListenV1UtteranceEnd; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Consumer; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okio.ByteString; - -/** - * WebSocket client for the v1 channel. - * Provides real-time bidirectional communication with strongly-typed messages. - */ -public class V1WebSocketClient implements AutoCloseable { - protected final ClientOptions clientOptions; - - private final ObjectMapper objectMapper; - - private final OkHttpClient okHttpClient; - - private ScheduledExecutorService timeoutExecutor; - - private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; - - private volatile Runnable onConnectedHandler; - - private volatile Consumer onDisconnectedHandler; - - private volatile Consumer onErrorHandler; - - private volatile Consumer onMessageHandler; - - private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; - - private CompletableFuture connectionFuture; - - private ReconnectingWebSocketListener reconnectingListener; - - private volatile Consumer resultsHandler; - - private volatile Consumer metadataHandler; - - private volatile Consumer utteranceEndHandler; - - private volatile Consumer speechStartedHandler; - - /** - * Creates a new async WebSocket client for the v1 channel. - */ - public V1WebSocketClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.objectMapper = ObjectMappers.JSON_MAPPER; - this.okHttpClient = clientOptions.httpClient(); - } - - /** - * Establishes the WebSocket connection asynchronously with automatic reconnection. - * @return a CompletableFuture that completes when the connection is established - * @param options connection options including query parameters - */ - public CompletableFuture connect(V1ConnectOptions options) { - connectionFuture = new CompletableFuture<>(); - String baseUrl = clientOptions.environment().getProductionURL(); - String fullPath = "/v1/listen"; - if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { - fullPath = fullPath.substring(1); - } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { - fullPath = "/" + fullPath; - } - // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing - if (baseUrl.startsWith("wss://")) { - baseUrl = "https://" + baseUrl.substring(6); - } else if (baseUrl.startsWith("ws://")) { - baseUrl = "http://" + baseUrl.substring(5); - } - HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); - if (parsedUrl == null) { - throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); - } - HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); - if (options.getCallback() != null && options.getCallback().isPresent()) { - urlBuilder.addQueryParameter( - "callback", String.valueOf(options.getCallback().get())); - } - if (options.getCallbackMethod() != null && options.getCallbackMethod().isPresent()) { - urlBuilder.addQueryParameter( - "callback_method", - String.valueOf(options.getCallbackMethod().get())); - } - if (options.getChannels() != null && options.getChannels().isPresent()) { - urlBuilder.addQueryParameter( - "channels", String.valueOf(options.getChannels().get())); - } - if (options.getDetectEntities() != null && options.getDetectEntities().isPresent()) { - urlBuilder.addQueryParameter( - "detect_entities", - String.valueOf(options.getDetectEntities().get())); - } - if (options.getDiarize() != null && options.getDiarize().isPresent()) { - urlBuilder.addQueryParameter( - "diarize", String.valueOf(options.getDiarize().get())); - } - if (options.getDiarizeModel() != null && options.getDiarizeModel().isPresent()) { - urlBuilder.addQueryParameter( - "diarize_model", String.valueOf(options.getDiarizeModel().get())); - } - if (options.getDictation() != null && options.getDictation().isPresent()) { - urlBuilder.addQueryParameter( - "dictation", String.valueOf(options.getDictation().get())); - } - if (options.getEncoding() != null && options.getEncoding().isPresent()) { - urlBuilder.addQueryParameter( - "encoding", String.valueOf(options.getEncoding().get())); - } - if (options.getEndpointing() != null && options.getEndpointing().isPresent()) { - urlBuilder.addQueryParameter( - "endpointing", String.valueOf(options.getEndpointing().get())); - } - if (options.getExtra() != null && options.getExtra().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "extra", options.getExtra().get().get(), true); - } - if (options.getInterimResults() != null && options.getInterimResults().isPresent()) { - urlBuilder.addQueryParameter( - "interim_results", - String.valueOf(options.getInterimResults().get())); - } - if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "keyterm", options.getKeyterm().get().get(), true); - } - if (options.getKeywords() != null && options.getKeywords().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "keywords", options.getKeywords().get().get(), true); - } - if (options.getLanguage() != null && options.getLanguage().isPresent()) { - urlBuilder.addQueryParameter( - "language", String.valueOf(options.getLanguage().get())); - } - if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { - urlBuilder.addQueryParameter( - "mip_opt_out", String.valueOf(options.getMipOptOut().get())); - } - urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); - if (options.getMultichannel() != null && options.getMultichannel().isPresent()) { - urlBuilder.addQueryParameter( - "multichannel", String.valueOf(options.getMultichannel().get())); - } - if (options.getNumerals() != null && options.getNumerals().isPresent()) { - urlBuilder.addQueryParameter( - "numerals", String.valueOf(options.getNumerals().get())); - } - if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { - urlBuilder.addQueryParameter( - "profanity_filter", - String.valueOf(options.getProfanityFilter().get())); - } - if (options.getPunctuate() != null && options.getPunctuate().isPresent()) { - urlBuilder.addQueryParameter( - "punctuate", String.valueOf(options.getPunctuate().get())); - } - if (options.getRedact() != null && options.getRedact().isPresent()) { - urlBuilder.addQueryParameter( - "redact", String.valueOf(options.getRedact().get())); - } - if (options.getReplace() != null && options.getReplace().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "replace", options.getReplace().get().get(), true); - } - if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { - urlBuilder.addQueryParameter( - "sample_rate", String.valueOf(options.getSampleRate().get())); - } - if (options.getSearch() != null && options.getSearch().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "search", options.getSearch().get().get(), true); - } - if (options.getSmartFormat() != null && options.getSmartFormat().isPresent()) { - urlBuilder.addQueryParameter( - "smart_format", String.valueOf(options.getSmartFormat().get())); - } - if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); - } - if (options.getUtteranceEndMs() != null && options.getUtteranceEndMs().isPresent()) { - urlBuilder.addQueryParameter( - "utterance_end_ms", - String.valueOf(options.getUtteranceEndMs().get())); - } - if (options.getVadEvents() != null && options.getVadEvents().isPresent()) { - urlBuilder.addQueryParameter( - "vad_events", String.valueOf(options.getVadEvents().get())); - } - if (options.getVersion() != null && options.getVersion().isPresent()) { - urlBuilder.addQueryParameter( - "version", String.valueOf(options.getVersion().get())); - } - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } - Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); - final Request request = requestBuilder.build(); - this.readyState = WebSocketReadyState.CONNECTING; - ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null - ? this.reconnectOptions - : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); - this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { - @Override - protected void onWebSocketOpen(WebSocket webSocket, Response response) { - readyState = WebSocketReadyState.OPEN; - if (onConnectedHandler != null) { - onConnectedHandler.run(); - } - connectionFuture.complete(null); - } - - @Override - protected void onWebSocketMessage(WebSocket webSocket, String text) { - handleIncomingMessage(text); - } - - @Override - protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) {} - - @Override - protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { - readyState = WebSocketReadyState.CLOSED; - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException(t)); - } - connectionFuture.completeExceptionally(t); - } - - @Override - protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { - readyState = WebSocketReadyState.CLOSED; - if (onDisconnectedHandler != null) { - onDisconnectedHandler.accept(new DisconnectReason(code, reason)); - } - } - }; - reconnectingListener.connect(); - return connectionFuture; - } - - /** - * Disconnects the WebSocket connection and releases resources. - */ - public void disconnect() { - if (reconnectingListener != null) { - reconnectingListener.disconnect(); - } - if (timeoutExecutor != null) { - timeoutExecutor.shutdownNow(); - timeoutExecutor = null; - } - } - - /** - * Gets the current state of the WebSocket connection. - * - * This provides the actual connection state, similar to the W3C WebSocket API. - * - * @return the current WebSocket ready state - */ - public WebSocketReadyState getReadyState() { - return readyState; - } - - /** - * Sends a ListenV1Media message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendMedia(ByteString message) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - // Use reconnecting listener's sendBinary method which handles queuing - reconnectingListener.sendBinary(message); - future.complete(null); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); - } - return future; - } - - /** - * Sends a ListenV1Finalize message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendFinalize(ListenV1Finalize message) { - return sendMessage(message); - } - - /** - * Sends a ListenV1CloseStream message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendCloseStream(ListenV1CloseStream message) { - return sendMessage(message); - } - - /** - * Sends a ListenV1KeepAlive message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendKeepAlive(ListenV1KeepAlive message) { - return sendMessage(message); - } - - /** - * Registers a handler for ListenV1Results messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onResults(Consumer handler) { - this.resultsHandler = handler; - } - - /** - * Registers a handler for ListenV1Metadata messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onMetadata(Consumer handler) { - this.metadataHandler = handler; - } - - /** - * Registers a handler for ListenV1UtteranceEnd messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onUtteranceEnd(Consumer handler) { - this.utteranceEndHandler = handler; - } - - /** - * Registers a handler for ListenV1SpeechStarted messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeechStarted(Consumer handler) { - this.speechStartedHandler = handler; - } - - /** - * Registers a handler called when the connection is established. - * @param handler the handler to invoke when connected - */ - public void onConnected(Runnable handler) { - this.onConnectedHandler = handler; - } - - /** - * Registers a handler called when the connection is closed. - * @param handler the handler to invoke when disconnected - */ - public void onDisconnected(Consumer handler) { - this.onDisconnectedHandler = handler; - } - - /** - * Registers a handler called when an error occurs. - * @param handler the handler to invoke on error - */ - public void onError(Consumer handler) { - this.onErrorHandler = handler; - } - - /** - * Registers a handler called for every incoming text message. - * The handler receives the raw JSON string before type-specific dispatch. - * @param handler the handler to invoke with the raw message JSON - */ - public void onMessage(Consumer handler) { - this.onMessageHandler = handler; - } - - /** - * Configures reconnection behavior. Must be called before {@link #connect}. - * - * @param options the reconnection options (backoff, retries, queue size) - */ - public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { - this.reconnectOptions = options; - } - - /** - * Closes this WebSocket client, releasing all resources. - * Equivalent to calling {@link #disconnect()}. - */ - @Override - public void close() { - disconnect(); - } - - /** - * Ensures the WebSocket is connected and ready to send messages. - * @throws IllegalStateException if the socket is not connected or not open - */ - private void assertSocketIsOpen() { - if (reconnectingListener.getWebSocket() == null) { - throw new IllegalStateException("WebSocket is not connected. Call connect() first."); - } - if (readyState != WebSocketReadyState.OPEN) { - throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); - } - } - - private CompletableFuture sendMessage(Object body) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - String json = objectMapper.writeValueAsString(body); - // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json); - future.complete(null); - } catch (IllegalStateException e) { - future.completeExceptionally(e); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send message", e)); - } - return future; - } - - private void handleIncomingMessage(String json) { - try { - if (onMessageHandler != null) { - onMessageHandler.accept(json); - } - JsonNode node = objectMapper.readTree(json); - if (node == null || node.isNull()) { - throw new IllegalArgumentException("Received null or invalid JSON message"); - } - if (node.has("transaction_key") - && node.has("request_id") - && node.has("sha256") - && node.has("created") - && node.has("duration") - && node.has("channels") - && "Metadata".equals(node.path("type").asText())) { - ListenV1Metadata metadataHandlerEvent = null; - try { - metadataHandlerEvent = objectMapper.treeToValue(node, ListenV1Metadata.class); - } catch (Exception e) { - } - if (metadataHandlerEvent != null) { - if (metadataHandler != null) { - metadataHandler.accept(metadataHandlerEvent); - } - return; - } - } - if (node.has("channel_index") - && node.has("duration") - && node.has("start") - && node.has("channel") - && node.has("metadata") - && "Results".equals(node.path("type").asText())) { - ListenV1Results resultsHandlerEvent = null; - try { - resultsHandlerEvent = objectMapper.treeToValue(node, ListenV1Results.class); - } catch (Exception e) { - } - if (resultsHandlerEvent != null) { - if (resultsHandler != null) { - resultsHandler.accept(resultsHandlerEvent); - } - return; - } - } - if (node.has("channel") - && node.has("last_word_end") - && "UtteranceEnd".equals(node.path("type").asText())) { - ListenV1UtteranceEnd utteranceEndHandlerEvent = null; - try { - utteranceEndHandlerEvent = objectMapper.treeToValue(node, ListenV1UtteranceEnd.class); - } catch (Exception e) { - } - if (utteranceEndHandlerEvent != null) { - if (utteranceEndHandler != null) { - utteranceEndHandler.accept(utteranceEndHandlerEvent); - } - return; - } - } - if (node.has("channel") - && node.has("timestamp") - && "SpeechStarted".equals(node.path("type").asText())) { - ListenV1SpeechStarted speechStartedHandlerEvent = null; - try { - speechStartedHandlerEvent = objectMapper.treeToValue(node, ListenV1SpeechStarted.class); - } catch (Exception e) { - } - if (speechStartedHandlerEvent != null) { - if (speechStartedHandler != null) { - speechStartedHandler.accept(speechStartedHandlerEvent); - } - return; - } - } - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException( - "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) - + "... Update your SDK version to support new message types.")); - } - } catch (Exception e) { - if (onErrorHandler != null) { - onErrorHandler.accept(e); - } - } - } -} diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java index e6308e2..c9f2f35 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof ListenV2CloseStream; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak deleted file mode 100644 index 8f7496d..0000000 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.listen.v2.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = ListenV2CloseStream.Builder.class) -public final class ListenV2CloseStream { - private final Map additionalProperties; - - private ListenV2CloseStream(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier - */ - @JsonProperty("type") - public String getType() { - return "CloseStream"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof ListenV2CloseStream; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(ListenV2CloseStream other) { - return this; - } - - public ListenV2CloseStream build() { - return new ListenV2CloseStream(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java index c3dbd38..894bd3c 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof ListenV2ForceEndTurn; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak deleted file mode 100644 index 99471a5..0000000 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.listen.v2.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = ListenV2ForceEndTurn.Builder.class) -public final class ListenV2ForceEndTurn { - private final Map additionalProperties; - - private ListenV2ForceEndTurn(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier - */ - @JsonProperty("type") - public String getType() { - return "ForceEndTurn"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof ListenV2ForceEndTurn; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(ListenV2ForceEndTurn other) { - return this; - } - - public ListenV2ForceEndTurn build() { - return new ListenV2ForceEndTurn(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java index 84bf5df..5f55fea 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java @@ -6,6 +6,7 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -22,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -59,6 +61,8 @@ public class V2WebSocketClient implements AutoCloseable { private ReconnectingWebSocketListener reconnectingListener; + private final AtomicReference closeStreamSocket = new AtomicReference<>(); + private volatile Consumer connectedHandler; private volatile Consumer turnInfoHandler; @@ -87,6 +91,7 @@ public V2WebSocketClient(ClientOptions clientOptions) { */ public CompletableFuture connect(V2ConnectOptions options) { connectionFuture = new CompletableFuture<>(); + closeStreamSocket.set(null); String baseUrl = clientOptions.environment().getProductionURL(); String fullPath = "/v2/listen"; if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { @@ -129,12 +134,12 @@ public CompletableFuture connect(V2ConnectOptions options) { "eot_timeout_ms", String.valueOf(options.getEotTimeoutMs().get())); } if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - urlBuilder.addQueryParameter( - "keyterm", String.valueOf(options.getKeyterm().get())); + QueryStringMapper.addQueryParameter( + urlBuilder, "keyterm", options.getKeyterm().get().get(), true); } if (options.getLanguageHint() != null && options.getLanguageHint().isPresent()) { - urlBuilder.addQueryParameter( - "language_hint", String.valueOf(options.getLanguageHint().get())); + QueryStringMapper.addQueryParameter( + urlBuilder, "language_hint", options.getLanguageHint().get().get(), true); } if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { urlBuilder.addQueryParameter( @@ -154,7 +159,14 @@ public CompletableFuture connect(V2ConnectOptions options) { "mip_opt_out", String.valueOf(options.getMipOptOut().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); + QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); + } + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); @@ -182,6 +194,7 @@ public CompletableFuture connect(V2ConnectOptions options) { clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { + closeStreamSocket.set(null); readyState = WebSocketReadyState.OPEN; if (onConnectedHandler != null) { onConnectedHandler.run(); @@ -213,6 +226,12 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { onDisconnectedHandler.accept(new DisconnectReason(code, reason)); } } + + @Override + protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, String reason) { + return super.shouldReconnectAfterClose(webSocket, code, reason) + && (code != 1005 || closeStreamSocket.get() != webSocket); + } }; clientOptions.registerWebSocket(this); reconnectingListener.connect(); @@ -263,12 +282,13 @@ public CompletableFuture sendMedia(ByteString message) { } /** - * Sends a ListenV2CloseStream message to the server asynchronously. + * Sends a ListenV2CloseStream message to the server asynchronously. A queued CloseStream does + * not suppress reconnects because it might never reach the server. * @param message the message to send * @return a CompletableFuture that completes when the message is sent */ public CompletableFuture sendCloseStream(ListenV2CloseStream message) { - return sendMessage(message); + return sendMessage(message, closeStreamSocket::set); } /** @@ -402,12 +422,16 @@ private void assertSocketIsOpen() { } private CompletableFuture sendMessage(Object body) { + return sendMessage(body, null); + } + + private CompletableFuture sendMessage(Object body, Consumer onSent) { CompletableFuture future = new CompletableFuture<>(); try { assertSocketIsOpen(); String json = objectMapper.writeValueAsString(body); // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json); + reconnectingListener.send(json, onSent); future.complete(null); } catch (IllegalStateException e) { future.completeExceptionally(e); @@ -528,11 +552,8 @@ private void handleIncomingMessage(String json) { return; } } - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException( - "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) - + "... Update your SDK version to support new message types.")); - } + // The raw frame is already delivered to onMessage(String), so ignore unknown typed + // frames for forward compatibility with newly added server control messages. } catch (Exception e) { if (onErrorHandler != null) { onErrorHandler.accept(e); diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak deleted file mode 100644 index b2dee9b..0000000 --- a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak +++ /dev/null @@ -1,532 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.listen.v2.websocket; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ClientOptions; -import com.deepgram.core.DisconnectReason; -import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; -import com.deepgram.core.ReconnectingWebSocketListener; -import com.deepgram.core.RequestOptions; -import com.deepgram.core.WebSocketReadyState; -import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; -import com.deepgram.resources.listen.v2.types.ListenV2Configure; -import com.deepgram.resources.listen.v2.types.ListenV2ConfigureFailure; -import com.deepgram.resources.listen.v2.types.ListenV2ConfigureSuccess; -import com.deepgram.resources.listen.v2.types.ListenV2Connected; -import com.deepgram.resources.listen.v2.types.ListenV2FatalError; -import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; -import com.deepgram.resources.listen.v2.types.ListenV2TurnInfo; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okio.ByteString; - -/** - * WebSocket client for the v2 channel. - * Provides real-time bidirectional communication with strongly-typed messages. - */ -public class V2WebSocketClient implements AutoCloseable { - protected final ClientOptions clientOptions; - - private final ObjectMapper objectMapper; - - private final OkHttpClient okHttpClient; - - private ScheduledExecutorService timeoutExecutor; - - private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; - - private volatile Runnable onConnectedHandler; - - private volatile Consumer onDisconnectedHandler; - - private volatile Consumer onErrorHandler; - - private volatile Consumer onMessageHandler; - - private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; - - private CompletableFuture connectionFuture; - - private ReconnectingWebSocketListener reconnectingListener; - - private final AtomicReference closeStreamSocket = new AtomicReference<>(); - - private volatile Consumer connectedHandler; - - private volatile Consumer turnInfoHandler; - - private volatile Consumer configureSuccessHandler; - - private volatile Consumer configureFailureHandler; - - private volatile Consumer errorHandler; - - /** - * Creates a new async WebSocket client for the v2 channel. - */ - public V2WebSocketClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.objectMapper = ObjectMappers.JSON_MAPPER; - this.okHttpClient = clientOptions.httpClient(); - } - - /** - * Establishes the WebSocket connection asynchronously with automatic reconnection. - * @return a CompletableFuture that completes when the connection is established - * @param options connection options including query parameters - */ - public CompletableFuture connect(V2ConnectOptions options) { - connectionFuture = new CompletableFuture<>(); - closeStreamSocket.set(null); - String baseUrl = clientOptions.environment().getProductionURL(); - String fullPath = "/v2/listen"; - if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { - fullPath = fullPath.substring(1); - } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { - fullPath = "/" + fullPath; - } - // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing - if (baseUrl.startsWith("wss://")) { - baseUrl = "https://" + baseUrl.substring(6); - } else if (baseUrl.startsWith("ws://")) { - baseUrl = "http://" + baseUrl.substring(5); - } - HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); - if (parsedUrl == null) { - throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); - } - HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); - urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); - if (options.getEncoding() != null && options.getEncoding().isPresent()) { - urlBuilder.addQueryParameter( - "encoding", String.valueOf(options.getEncoding().get())); - } - if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { - urlBuilder.addQueryParameter( - "sample_rate", String.valueOf(options.getSampleRate().get())); - } - if (options.getEagerEotThreshold() != null - && options.getEagerEotThreshold().isPresent()) { - urlBuilder.addQueryParameter( - "eager_eot_threshold", - String.valueOf(options.getEagerEotThreshold().get())); - } - if (options.getEotThreshold() != null && options.getEotThreshold().isPresent()) { - urlBuilder.addQueryParameter( - "eot_threshold", String.valueOf(options.getEotThreshold().get())); - } - if (options.getEotTimeoutMs() != null && options.getEotTimeoutMs().isPresent()) { - urlBuilder.addQueryParameter( - "eot_timeout_ms", String.valueOf(options.getEotTimeoutMs().get())); - } - if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "keyterm", options.getKeyterm().get().get(), true); - } - if (options.getLanguageHint() != null && options.getLanguageHint().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "language_hint", options.getLanguageHint().get().get(), true); - } - if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { - urlBuilder.addQueryParameter( - "profanity_filter", - String.valueOf(options.getProfanityFilter().get())); - } - if (options.getNumerals() != null && options.getNumerals().isPresent()) { - urlBuilder.addQueryParameter( - "numerals", String.valueOf(options.getNumerals().get())); - } - if (options.getRedact() != null && options.getRedact().isPresent()) { - urlBuilder.addQueryParameter( - "redact", String.valueOf(options.getRedact().get())); - } - if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { - urlBuilder.addQueryParameter( - "mip_opt_out", String.valueOf(options.getMipOptOut().get())); - } - if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); - } - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } - Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); - final Request request = requestBuilder.build(); - this.readyState = WebSocketReadyState.CONNECTING; - ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null - ? this.reconnectOptions - : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); - this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { - @Override - protected void onWebSocketOpen(WebSocket webSocket, Response response) { - closeStreamSocket.set(null); - readyState = WebSocketReadyState.OPEN; - if (onConnectedHandler != null) { - onConnectedHandler.run(); - } - connectionFuture.complete(null); - } - - @Override - protected void onWebSocketMessage(WebSocket webSocket, String text) { - handleIncomingMessage(text); - } - - @Override - protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) {} - - @Override - protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { - readyState = WebSocketReadyState.CLOSED; - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException(t)); - } - connectionFuture.completeExceptionally(t); - } - - @Override - protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { - readyState = WebSocketReadyState.CLOSED; - if (onDisconnectedHandler != null) { - onDisconnectedHandler.accept(new DisconnectReason(code, reason)); - } - } - - @Override - protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, String reason) { - return super.shouldReconnectAfterClose(webSocket, code, reason) - && (code != 1005 || closeStreamSocket.get() != webSocket); - } - }; - reconnectingListener.connect(); - return connectionFuture; - } - - /** - * Disconnects the WebSocket connection and releases resources. - */ - public void disconnect() { - if (reconnectingListener != null) { - reconnectingListener.disconnect(); - } - if (timeoutExecutor != null) { - timeoutExecutor.shutdownNow(); - timeoutExecutor = null; - } - } - - /** - * Gets the current state of the WebSocket connection. - * - * This provides the actual connection state, similar to the W3C WebSocket API. - * - * @return the current WebSocket ready state - */ - public WebSocketReadyState getReadyState() { - return readyState; - } - - /** - * Sends a ListenV2Media message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendMedia(ByteString message) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - // Use reconnecting listener's sendBinary method which handles queuing - reconnectingListener.sendBinary(message); - future.complete(null); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); - } - return future; - } - - /** - * Sends a ListenV2CloseStream message to the server asynchronously. A queued CloseStream does - * not suppress reconnects because it might never reach the server. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendCloseStream(ListenV2CloseStream message) { - return sendMessage(message, closeStreamSocket::set); - } - - /** - * Sends a ListenV2ForceEndTurn message to the server asynchronously. - * - *

Sending this with no turn in progress is not an error. The server answers with a - * {@code Warning} frame carrying code {@code FORCE_END_TURN_NO_ACTIVE_TURN} and ignores the - * request. Listen V2 has no typed warning event yet, so that frame is delivered as raw JSON - * to {@link #onMessage(java.util.function.Consumer)}. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendForceEndTurn(ListenV2ForceEndTurn message) { - return sendMessage(message); - } - - /** - * Sends a ListenV2Configure message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendConfigure(ListenV2Configure message) { - return sendMessage(message); - } - - /** - * Registers a handler for ListenV2Connected messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConnected(Consumer handler) { - this.connectedHandler = handler; - } - - /** - * Registers a handler for ListenV2TurnInfo messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onTurnInfo(Consumer handler) { - this.turnInfoHandler = handler; - } - - /** - * Registers a handler for ListenV2ConfigureSuccess messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConfigureSuccess(Consumer handler) { - this.configureSuccessHandler = handler; - } - - /** - * Registers a handler for ListenV2ConfigureFailure messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConfigureFailure(Consumer handler) { - this.configureFailureHandler = handler; - } - - /** - * Registers a handler for ListenV2FatalError messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onErrorMessage(Consumer handler) { - this.errorHandler = handler; - } - - /** - * Registers a handler called when the connection is established. - * @param handler the handler to invoke when connected - */ - public void onConnected(Runnable handler) { - this.onConnectedHandler = handler; - } - - /** - * Registers a handler called when the connection is closed. An empty peer close frame is - * reported with code 1005, even though the SDK acknowledges it with code 1000. - * @param handler the handler to invoke when disconnected - */ - public void onDisconnected(Consumer handler) { - this.onDisconnectedHandler = handler; - } - - /** - * Registers a handler called when an error occurs. - * @param handler the handler to invoke on error - */ - public void onError(Consumer handler) { - this.onErrorHandler = handler; - } - - /** - * Registers a handler called for every incoming text message. - * The handler receives the raw JSON string before type-specific dispatch. - * @param handler the handler to invoke with the raw message JSON - */ - public void onMessage(Consumer handler) { - this.onMessageHandler = handler; - } - - /** - * Configures reconnection behavior. Must be called before {@link #connect}. - * - * @param options the reconnection options (backoff, retries, queue size) - */ - public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { - this.reconnectOptions = options; - } - - /** - * Closes this WebSocket client, releasing all resources. - * Equivalent to calling {@link #disconnect()}. - */ - @Override - public void close() { - disconnect(); - } - - /** - * Ensures the WebSocket is connected and ready to send messages. - * @throws IllegalStateException if the socket is not connected or not open - */ - private void assertSocketIsOpen() { - if (reconnectingListener.getWebSocket() == null) { - throw new IllegalStateException("WebSocket is not connected. Call connect() first."); - } - if (readyState != WebSocketReadyState.OPEN) { - throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); - } - } - - private CompletableFuture sendMessage(Object body) { - return sendMessage(body, null); - } - - private CompletableFuture sendMessage(Object body, Consumer onSent) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - String json = objectMapper.writeValueAsString(body); - // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json, onSent); - future.complete(null); - } catch (IllegalStateException e) { - future.completeExceptionally(e); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send message", e)); - } - return future; - } - - private void handleIncomingMessage(String json) { - try { - if (onMessageHandler != null) { - onMessageHandler.accept(json); - } - JsonNode node = objectMapper.readTree(json); - if (node == null || node.isNull()) { - throw new IllegalArgumentException("Received null or invalid JSON message"); - } - if (node.has("request_id") - && node.has("sequence_id") - && node.has("event") - && node.has("turn_index") - && node.has("audio_window_start") - && node.has("audio_window_end") - && node.has("transcript") - && node.has("words") - && node.has("end_of_turn_confidence") - && "TurnInfo".equals(node.path("type").asText())) { - ListenV2TurnInfo turnInfoHandlerEvent = null; - try { - turnInfoHandlerEvent = objectMapper.treeToValue(node, ListenV2TurnInfo.class); - } catch (Exception e) { - } - if (turnInfoHandlerEvent != null) { - if (turnInfoHandler != null) { - turnInfoHandler.accept(turnInfoHandlerEvent); - } - return; - } - } - if (node.has("request_id") - && node.has("thresholds") - && node.has("keyterms") - && node.has("sequence_id") - && "ConfigureSuccess".equals(node.path("type").asText())) { - ListenV2ConfigureSuccess configureSuccessHandlerEvent = null; - try { - configureSuccessHandlerEvent = objectMapper.treeToValue(node, ListenV2ConfigureSuccess.class); - } catch (Exception e) { - } - if (configureSuccessHandlerEvent != null) { - if (configureSuccessHandler != null) { - configureSuccessHandler.accept(configureSuccessHandlerEvent); - } - return; - } - } - if (node.has("sequence_id") - && node.has("code") - && node.has("description") - && "Error".equals(node.path("type").asText())) { - ListenV2FatalError errorHandlerEvent = null; - try { - errorHandlerEvent = objectMapper.treeToValue(node, ListenV2FatalError.class); - } catch (Exception e) { - } - if (errorHandlerEvent != null) { - if (errorHandler != null) { - errorHandler.accept(errorHandlerEvent); - } - return; - } - } - if (node.has("request_id") - && node.has("sequence_id") - && "Connected".equals(node.path("type").asText())) { - ListenV2Connected connectedHandlerEvent = null; - try { - connectedHandlerEvent = objectMapper.treeToValue(node, ListenV2Connected.class); - } catch (Exception e) { - } - if (connectedHandlerEvent != null) { - if (connectedHandler != null) { - connectedHandler.accept(connectedHandlerEvent); - } - return; - } - } - if (node.has("request_id") - && node.has("sequence_id") - && "ConfigureFailure".equals(node.path("type").asText())) { - ListenV2ConfigureFailure configureFailureHandlerEvent = null; - try { - configureFailureHandlerEvent = objectMapper.treeToValue(node, ListenV2ConfigureFailure.class); - } catch (Exception e) { - } - if (configureFailureHandlerEvent != null) { - if (configureFailureHandler != null) { - configureFailureHandler.accept(configureFailureHandlerEvent); - } - return; - } - } - // The raw frame is already delivered to onMessage(String), so ignore unknown typed - // frames for forward compatibility with newly added server control messages. - } catch (Exception e) { - if (onErrorHandler != null) { - onErrorHandler.accept(e); - } - } - } -} diff --git a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java index 6dd298a..e6824a3 100644 --- a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java @@ -6,6 +6,7 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -122,6 +123,13 @@ public CompletableFuture connect(V1ConnectOptions options) { urlBuilder.addQueryParameter( "speed", String.valueOf(options.getSpeed().get())); } + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); diff --git a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak deleted file mode 100644 index ae271f2..0000000 --- a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak +++ /dev/null @@ -1,458 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.speak.v1.websocket; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ClientOptions; -import com.deepgram.core.DisconnectReason; -import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; -import com.deepgram.core.ReconnectingWebSocketListener; -import com.deepgram.core.RequestOptions; -import com.deepgram.core.WebSocketReadyState; -import com.deepgram.resources.speak.v1.types.SpeakV1Clear; -import com.deepgram.resources.speak.v1.types.SpeakV1Cleared; -import com.deepgram.resources.speak.v1.types.SpeakV1Close; -import com.deepgram.resources.speak.v1.types.SpeakV1Flush; -import com.deepgram.resources.speak.v1.types.SpeakV1Flushed; -import com.deepgram.resources.speak.v1.types.SpeakV1Metadata; -import com.deepgram.resources.speak.v1.types.SpeakV1Text; -import com.deepgram.resources.speak.v1.types.SpeakV1Warning; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Consumer; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okio.ByteString; - -/** - * WebSocket client for the v1 channel. - * Provides real-time bidirectional communication with strongly-typed messages. - */ -public class V1WebSocketClient implements AutoCloseable { - protected final ClientOptions clientOptions; - - private final ObjectMapper objectMapper; - - private final OkHttpClient okHttpClient; - - private ScheduledExecutorService timeoutExecutor; - - private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; - - private volatile Runnable onConnectedHandler; - - private volatile Consumer onDisconnectedHandler; - - private volatile Consumer onErrorHandler; - - private volatile Consumer onMessageHandler; - - private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; - - private CompletableFuture connectionFuture; - - private ReconnectingWebSocketListener reconnectingListener; - - private volatile Consumer speakV1AudioHandler; - - private volatile Consumer metadataHandler; - - private volatile Consumer flushedHandler; - - private volatile Consumer clearedHandler; - - private volatile Consumer warningHandler; - - /** - * Creates a new async WebSocket client for the v1 channel. - */ - public V1WebSocketClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.objectMapper = ObjectMappers.JSON_MAPPER; - this.okHttpClient = clientOptions.httpClient(); - } - - /** - * Establishes the WebSocket connection asynchronously with automatic reconnection. - * @return a CompletableFuture that completes when the connection is established - * @param options connection options including query parameters - */ - public CompletableFuture connect(V1ConnectOptions options) { - connectionFuture = new CompletableFuture<>(); - String baseUrl = clientOptions.environment().getProductionURL(); - String fullPath = "/v1/speak"; - if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { - fullPath = fullPath.substring(1); - } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { - fullPath = "/" + fullPath; - } - // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing - if (baseUrl.startsWith("wss://")) { - baseUrl = "https://" + baseUrl.substring(6); - } else if (baseUrl.startsWith("ws://")) { - baseUrl = "http://" + baseUrl.substring(5); - } - HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); - if (parsedUrl == null) { - throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); - } - HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); - if (options.getEncoding() != null && options.getEncoding().isPresent()) { - urlBuilder.addQueryParameter( - "encoding", String.valueOf(options.getEncoding().get())); - } - if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { - urlBuilder.addQueryParameter( - "mip_opt_out", String.valueOf(options.getMipOptOut().get())); - } - if (options.getModel() != null && options.getModel().isPresent()) { - urlBuilder.addQueryParameter( - "model", String.valueOf(options.getModel().get())); - } - if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { - urlBuilder.addQueryParameter( - "sample_rate", String.valueOf(options.getSampleRate().get())); - } - if (options.getSpeed() != null && options.getSpeed().isPresent()) { - urlBuilder.addQueryParameter( - "speed", String.valueOf(options.getSpeed().get())); - } - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } - Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); - final Request request = requestBuilder.build(); - this.readyState = WebSocketReadyState.CONNECTING; - ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null - ? this.reconnectOptions - : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); - this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { - @Override - protected void onWebSocketOpen(WebSocket webSocket, Response response) { - readyState = WebSocketReadyState.OPEN; - if (onConnectedHandler != null) { - onConnectedHandler.run(); - } - connectionFuture.complete(null); - } - - @Override - protected void onWebSocketMessage(WebSocket webSocket, String text) { - handleIncomingMessage(text); - } - - @Override - protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { - if (speakV1AudioHandler != null) { - speakV1AudioHandler.accept(bytes); - } - } - - @Override - protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { - readyState = WebSocketReadyState.CLOSED; - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException(t)); - } - connectionFuture.completeExceptionally(t); - } - - @Override - protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { - readyState = WebSocketReadyState.CLOSED; - if (onDisconnectedHandler != null) { - onDisconnectedHandler.accept(new DisconnectReason(code, reason)); - } - } - }; - reconnectingListener.connect(); - return connectionFuture; - } - - /** - * Establishes the WebSocket connection asynchronously with default options. - * @return a CompletableFuture that completes when the connection is established - */ - public CompletableFuture connect() { - return connect(V1ConnectOptions.builder().build()); - } - - /** - * Disconnects the WebSocket connection and releases resources. - */ - public void disconnect() { - if (reconnectingListener != null) { - reconnectingListener.disconnect(); - } - if (timeoutExecutor != null) { - timeoutExecutor.shutdownNow(); - timeoutExecutor = null; - } - } - - /** - * Gets the current state of the WebSocket connection. - * - * This provides the actual connection state, similar to the W3C WebSocket API. - * - * @return the current WebSocket ready state - */ - public WebSocketReadyState getReadyState() { - return readyState; - } - - /** - * Sends a SpeakV1Text message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendText(SpeakV1Text message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV1Flush message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendFlush(SpeakV1Flush message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV1Clear message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendClear(SpeakV1Clear message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV1Close message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendClose(SpeakV1Close message) { - return sendMessage(message); - } - - /** - * Registers a handler for SpeakV1Audio messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeakV1Audio(Consumer handler) { - this.speakV1AudioHandler = handler; - } - - /** - * Registers a handler for SpeakV1Metadata messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onMetadata(Consumer handler) { - this.metadataHandler = handler; - } - - /** - * Registers a handler for SpeakV1Flushed messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onFlushed(Consumer handler) { - this.flushedHandler = handler; - } - - /** - * Registers a handler for SpeakV1Cleared messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onCleared(Consumer handler) { - this.clearedHandler = handler; - } - - /** - * Registers a handler for SpeakV1Warning messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onWarning(Consumer handler) { - this.warningHandler = handler; - } - - /** - * Registers a handler called when the connection is established. - * @param handler the handler to invoke when connected - */ - public void onConnected(Runnable handler) { - this.onConnectedHandler = handler; - } - - /** - * Registers a handler called when the connection is closed. - * @param handler the handler to invoke when disconnected - */ - public void onDisconnected(Consumer handler) { - this.onDisconnectedHandler = handler; - } - - /** - * Registers a handler called when an error occurs. - * @param handler the handler to invoke on error - */ - public void onError(Consumer handler) { - this.onErrorHandler = handler; - } - - /** - * Registers a handler called for every incoming text message. - * The handler receives the raw JSON string before type-specific dispatch. - * @param handler the handler to invoke with the raw message JSON - */ - public void onMessage(Consumer handler) { - this.onMessageHandler = handler; - } - - /** - * Configures reconnection behavior. Must be called before {@link #connect}. - * - * @param options the reconnection options (backoff, retries, queue size) - */ - public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { - this.reconnectOptions = options; - } - - /** - * Closes this WebSocket client, releasing all resources. - * Equivalent to calling {@link #disconnect()}. - */ - @Override - public void close() { - disconnect(); - } - - /** - * Ensures the WebSocket is connected and ready to send messages. - * @throws IllegalStateException if the socket is not connected or not open - */ - private void assertSocketIsOpen() { - if (reconnectingListener.getWebSocket() == null) { - throw new IllegalStateException("WebSocket is not connected. Call connect() first."); - } - if (readyState != WebSocketReadyState.OPEN) { - throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); - } - } - - private CompletableFuture sendMessage(Object body) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - String json = objectMapper.writeValueAsString(body); - // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json); - future.complete(null); - } catch (IllegalStateException e) { - future.completeExceptionally(e); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send message", e)); - } - return future; - } - - private void handleIncomingMessage(String json) { - try { - if (onMessageHandler != null) { - onMessageHandler.accept(json); - } - JsonNode node = objectMapper.readTree(json); - if (node == null || node.isNull()) { - throw new IllegalArgumentException("Received null or invalid JSON message"); - } - if (node.has("request_id") - && node.has("model_name") - && node.has("model_version") - && node.has("model_uuid") - && "Metadata".equals(node.path("type").asText())) { - SpeakV1Metadata metadataHandlerEvent = null; - try { - metadataHandlerEvent = objectMapper.treeToValue(node, SpeakV1Metadata.class); - } catch (Exception e) { - } - if (metadataHandlerEvent != null) { - if (metadataHandler != null) { - metadataHandler.accept(metadataHandlerEvent); - } - return; - } - } - if (node.has("description") - && node.has("code") - && "Warning".equals(node.path("type").asText())) { - SpeakV1Warning warningHandlerEvent = null; - try { - warningHandlerEvent = objectMapper.treeToValue(node, SpeakV1Warning.class); - } catch (Exception e) { - } - if (warningHandlerEvent != null) { - if (warningHandler != null) { - warningHandler.accept(warningHandlerEvent); - } - return; - } - } - if (node.has("type") && node.has("sequence_id")) { - SpeakV1Flushed flushedHandlerEvent = null; - try { - flushedHandlerEvent = objectMapper.treeToValue(node, SpeakV1Flushed.class); - } catch (Exception e) { - } - if (flushedHandlerEvent != null) { - if (flushedHandler != null) { - flushedHandler.accept(flushedHandlerEvent); - } - return; - } - } - if (node.has("type") && node.has("sequence_id")) { - SpeakV1Cleared clearedHandlerEvent = null; - try { - clearedHandlerEvent = objectMapper.treeToValue(node, SpeakV1Cleared.class); - } catch (Exception e) { - } - if (clearedHandlerEvent != null) { - if (clearedHandler != null) { - clearedHandler.accept(clearedHandlerEvent); - } - return; - } - } - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException( - "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) - + "... Update your SDK version to support new message types.")); - } - } catch (Exception e) { - if (onErrorHandler != null) { - onErrorHandler.accept(e); - } - } - } -} diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java index 2e96a5c..7fecd79 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof SpeakV2Close; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak deleted file mode 100644 index 0479ff6..0000000 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.speak.v2.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SpeakV2Close.Builder.class) -public final class SpeakV2Close { - private final Map additionalProperties; - - private SpeakV2Close(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier - */ - @JsonProperty("type") - public String getType() { - return "Close"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SpeakV2Close; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SpeakV2Close other) { - return this; - } - - public SpeakV2Close build() { - return new SpeakV2Close(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java index acd717d..130da0d 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java @@ -36,6 +36,11 @@ public boolean equals(Object other) { return other instanceof SpeakV2Flush; } + @java.lang.Override + public int hashCode() { + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak deleted file mode 100644 index db0b1ec..0000000 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.speak.v2.types; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SpeakV2Flush.Builder.class) -public final class SpeakV2Flush { - private final Map additionalProperties; - - private SpeakV2Flush(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier - */ - @JsonProperty("type") - public String getType() { - return "Flush"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SpeakV2Flush; - } - - @java.lang.Override - public int hashCode() { - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SpeakV2Flush other) { - return this; - } - - public SpeakV2Flush build() { - return new SpeakV2Flush(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java index 07f6c36..b4e01f7 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java @@ -6,6 +6,7 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -143,7 +144,14 @@ public CompletableFuture connect(V2ConnectOptions options) { "mip_opt_out", String.valueOf(options.getMipOptOut().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); + QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); + } + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); @@ -611,11 +619,8 @@ private void handleIncomingMessage(String json) { return; } } - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException( - "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) - + "... Update your SDK version to support new message types.")); - } + // The raw frame is already delivered to onMessage(String), so ignore unknown typed + // frames for forward compatibility with newly added server control messages. } catch (Exception e) { if (onErrorHandler != null) { onErrorHandler.accept(e); diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak deleted file mode 100644 index a94145d..0000000 --- a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak +++ /dev/null @@ -1,621 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.speak.v2.websocket; - -// Manual patch - see .fernignore. - -import com.deepgram.core.ClientOptions; -import com.deepgram.core.DisconnectReason; -import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; -import com.deepgram.core.ReconnectingWebSocketListener; -import com.deepgram.core.RequestOptions; -import com.deepgram.core.WebSocketReadyState; -import com.deepgram.resources.speak.v2.types.SpeakV2Close; -import com.deepgram.resources.speak.v2.types.SpeakV2Configure; -import com.deepgram.resources.speak.v2.types.SpeakV2ConfigureFailure; -import com.deepgram.resources.speak.v2.types.SpeakV2ConfigureSuccess; -import com.deepgram.resources.speak.v2.types.SpeakV2Connected; -import com.deepgram.resources.speak.v2.types.SpeakV2Error; -import com.deepgram.resources.speak.v2.types.SpeakV2Flush; -import com.deepgram.resources.speak.v2.types.SpeakV2Flushed; -import com.deepgram.resources.speak.v2.types.SpeakV2Interrupt; -import com.deepgram.resources.speak.v2.types.SpeakV2SessionMetadata; -import com.deepgram.resources.speak.v2.types.SpeakV2Speak; -import com.deepgram.resources.speak.v2.types.SpeakV2SpeechInterrupted; -import com.deepgram.resources.speak.v2.types.SpeakV2SpeechMetadata; -import com.deepgram.resources.speak.v2.types.SpeakV2SpeechStarted; -import com.deepgram.resources.speak.v2.types.SpeakV2Warning; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Consumer; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okio.ByteString; - -/** - * WebSocket client for the v2 channel. - * Provides real-time bidirectional communication with strongly-typed messages. - */ -public class V2WebSocketClient implements AutoCloseable { - protected final ClientOptions clientOptions; - - private final ObjectMapper objectMapper; - - private final OkHttpClient okHttpClient; - - private ScheduledExecutorService timeoutExecutor; - - private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; - - private volatile Runnable onConnectedHandler; - - private volatile Consumer onDisconnectedHandler; - - private volatile Consumer onErrorHandler; - - private volatile Consumer onMessageHandler; - - private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; - - private CompletableFuture connectionFuture; - - private ReconnectingWebSocketListener reconnectingListener; - - private volatile Consumer speakV2AudioHandler; - - private volatile Consumer connectedHandler; - - private volatile Consumer speechStartedHandler; - - private volatile Consumer speechMetadataHandler; - - private volatile Consumer speechInterruptedHandler; - - private volatile Consumer flushedHandler; - - private volatile Consumer sessionMetadataHandler; - - private volatile Consumer configureSuccessHandler; - - private volatile Consumer configureFailureHandler; - - private volatile Consumer warningHandler; - - private volatile Consumer errorHandler; - - /** - * Creates a new async WebSocket client for the v2 channel. - */ - public V2WebSocketClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.objectMapper = ObjectMappers.JSON_MAPPER; - this.okHttpClient = clientOptions.httpClient(); - } - - /** - * Establishes the WebSocket connection asynchronously with automatic reconnection. - * @return a CompletableFuture that completes when the connection is established - * @param options connection options including query parameters - */ - public CompletableFuture connect(V2ConnectOptions options) { - connectionFuture = new CompletableFuture<>(); - String baseUrl = clientOptions.environment().getProductionURL(); - String fullPath = "/v2/speak"; - if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { - fullPath = fullPath.substring(1); - } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { - fullPath = "/" + fullPath; - } - // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing - if (baseUrl.startsWith("wss://")) { - baseUrl = "https://" + baseUrl.substring(6); - } else if (baseUrl.startsWith("ws://")) { - baseUrl = "http://" + baseUrl.substring(5); - } - HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); - if (parsedUrl == null) { - throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); - } - HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); - urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); - if (options.getEncoding() != null && options.getEncoding().isPresent()) { - urlBuilder.addQueryParameter( - "encoding", String.valueOf(options.getEncoding().get())); - } - if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { - urlBuilder.addQueryParameter( - "sample_rate", String.valueOf(options.getSampleRate().get())); - } - if (options.getSpeed() != null && options.getSpeed().isPresent()) { - urlBuilder.addQueryParameter( - "speed", String.valueOf(options.getSpeed().get())); - } - if (options.getExpressivity() != null && options.getExpressivity().isPresent()) { - urlBuilder.addQueryParameter( - "expressivity", String.valueOf(options.getExpressivity().get())); - } - if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { - urlBuilder.addQueryParameter( - "mip_opt_out", String.valueOf(options.getMipOptOut().get())); - } - if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter(urlBuilder, "tag", options.getTag().get().get(), true); - } - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } - Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); - final Request request = requestBuilder.build(); - this.readyState = WebSocketReadyState.CONNECTING; - ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null - ? this.reconnectOptions - : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); - this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { - @Override - protected void onWebSocketOpen(WebSocket webSocket, Response response) { - readyState = WebSocketReadyState.OPEN; - if (onConnectedHandler != null) { - onConnectedHandler.run(); - } - connectionFuture.complete(null); - } - - @Override - protected void onWebSocketMessage(WebSocket webSocket, String text) { - handleIncomingMessage(text); - } - - @Override - protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { - if (speakV2AudioHandler != null) { - speakV2AudioHandler.accept(bytes); - } - } - - @Override - protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { - readyState = WebSocketReadyState.CLOSED; - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException(t)); - } - connectionFuture.completeExceptionally(t); - } - - @Override - protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { - readyState = WebSocketReadyState.CLOSED; - if (onDisconnectedHandler != null) { - onDisconnectedHandler.accept(new DisconnectReason(code, reason)); - } - } - }; - reconnectingListener.connect(); - return connectionFuture; - } - - /** - * Disconnects the WebSocket connection and releases resources. - */ - public void disconnect() { - if (reconnectingListener != null) { - reconnectingListener.disconnect(); - } - if (timeoutExecutor != null) { - timeoutExecutor.shutdownNow(); - timeoutExecutor = null; - } - } - - /** - * Gets the current state of the WebSocket connection. - * - * This provides the actual connection state, similar to the W3C WebSocket API. - * - * @return the current WebSocket ready state - */ - public WebSocketReadyState getReadyState() { - return readyState; - } - - /** - * Sends a SpeakV2Speak message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendSpeak(SpeakV2Speak message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV2Flush message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendFlush(SpeakV2Flush message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV2Interrupt message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendInterrupt(SpeakV2Interrupt message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV2Configure message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendConfigure(SpeakV2Configure message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV2Close message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendClose(SpeakV2Close message) { - return sendMessage(message); - } - - /** - * Registers a handler for SpeakV2Audio messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeakV2Audio(Consumer handler) { - this.speakV2AudioHandler = handler; - } - - /** - * Registers a handler for SpeakV2Connected messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConnected(Consumer handler) { - this.connectedHandler = handler; - } - - /** - * Registers a handler for SpeakV2SpeechStarted messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeechStarted(Consumer handler) { - this.speechStartedHandler = handler; - } - - /** - * Registers a handler for SpeakV2SpeechMetadata messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeechMetadata(Consumer handler) { - this.speechMetadataHandler = handler; - } - - /** - * Registers a handler for SpeakV2SpeechInterrupted messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeechInterrupted(Consumer handler) { - this.speechInterruptedHandler = handler; - } - - /** - * Registers a handler for SpeakV2Flushed messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onFlushed(Consumer handler) { - this.flushedHandler = handler; - } - - /** - * Registers a handler for SpeakV2SessionMetadata messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSessionMetadata(Consumer handler) { - this.sessionMetadataHandler = handler; - } - - /** - * Registers a handler for SpeakV2ConfigureSuccess messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConfigureSuccess(Consumer handler) { - this.configureSuccessHandler = handler; - } - - /** - * Registers a handler for SpeakV2ConfigureFailure messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConfigureFailure(Consumer handler) { - this.configureFailureHandler = handler; - } - - /** - * Registers a handler for SpeakV2Warning messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onWarning(Consumer handler) { - this.warningHandler = handler; - } - - /** - * Registers a handler for SpeakV2Error messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onErrorMessage(Consumer handler) { - this.errorHandler = handler; - } - - /** - * Registers a handler called when the connection is established. - * @param handler the handler to invoke when connected - */ - public void onConnected(Runnable handler) { - this.onConnectedHandler = handler; - } - - /** - * Registers a handler called when the connection is closed. - * @param handler the handler to invoke when disconnected - */ - public void onDisconnected(Consumer handler) { - this.onDisconnectedHandler = handler; - } - - /** - * Registers a handler called when an error occurs. - * @param handler the handler to invoke on error - */ - public void onError(Consumer handler) { - this.onErrorHandler = handler; - } - - /** - * Registers a handler called for every incoming text message. - * The handler receives the raw JSON string before type-specific dispatch. - * @param handler the handler to invoke with the raw message JSON - */ - public void onMessage(Consumer handler) { - this.onMessageHandler = handler; - } - - /** - * Configures reconnection behavior. Must be called before {@link #connect}. - * - * @param options the reconnection options (backoff, retries, queue size) - */ - public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { - this.reconnectOptions = options; - } - - /** - * Closes this WebSocket client, releasing all resources. - * Equivalent to calling {@link #disconnect()}. - */ - @Override - public void close() { - disconnect(); - } - - /** - * Ensures the WebSocket is connected and ready to send messages. - * @throws IllegalStateException if the socket is not connected or not open - */ - private void assertSocketIsOpen() { - if (reconnectingListener.getWebSocket() == null) { - throw new IllegalStateException("WebSocket is not connected. Call connect() first."); - } - if (readyState != WebSocketReadyState.OPEN) { - throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); - } - } - - private CompletableFuture sendMessage(Object body) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - String json = objectMapper.writeValueAsString(body); - // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json); - future.complete(null); - } catch (IllegalStateException e) { - future.completeExceptionally(e); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send message", e)); - } - return future; - } - - private void handleIncomingMessage(String json) { - try { - if (onMessageHandler != null) { - onMessageHandler.accept(json); - } - JsonNode node = objectMapper.readTree(json); - if (node == null || node.isNull()) { - throw new IllegalArgumentException("Received null or invalid JSON message"); - } - if (node.has("speech_id") - && node.has("audio_duration_ms") - && node.has("input_character_count") - && node.has("billable_character_count") - && node.has("controls_applied") - && "SpeechMetadata".equals(node.path("type").asText())) { - SpeakV2SpeechMetadata speechMetadataHandlerEvent = null; - try { - speechMetadataHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechMetadata.class); - } catch (Exception e) { - } - if (speechMetadataHandlerEvent != null) { - if (speechMetadataHandler != null) { - speechMetadataHandler.accept(speechMetadataHandlerEvent); - } - return; - } - } - if (node.has("request_id") - && node.has("model_name") - && node.has("model_version") - && node.has("model_uuids") - && "Connected".equals(node.path("type").asText())) { - SpeakV2Connected connectedHandlerEvent = null; - try { - connectedHandlerEvent = objectMapper.treeToValue(node, SpeakV2Connected.class); - } catch (Exception e) { - } - if (connectedHandlerEvent != null) { - if (connectedHandler != null) { - connectedHandler.accept(connectedHandlerEvent); - } - return; - } - } - if (node.has("total_audio_duration_ms") - && node.has("total_input_character_count") - && node.has("total_billable_character_count") - && "SessionMetadata".equals(node.path("type").asText())) { - SpeakV2SessionMetadata sessionMetadataHandlerEvent = null; - try { - sessionMetadataHandlerEvent = objectMapper.treeToValue(node, SpeakV2SessionMetadata.class); - } catch (Exception e) { - } - if (sessionMetadataHandlerEvent != null) { - if (sessionMetadataHandler != null) { - sessionMetadataHandler.accept(sessionMetadataHandlerEvent); - } - return; - } - } - if (node.has("audio_played_ms") - && node.has("metadata") - && "SpeechInterrupted".equals(node.path("type").asText())) { - SpeakV2SpeechInterrupted speechInterruptedHandlerEvent = null; - try { - speechInterruptedHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechInterrupted.class); - } catch (Exception e) { - } - if (speechInterruptedHandlerEvent != null) { - if (speechInterruptedHandler != null) { - speechInterruptedHandler.accept(speechInterruptedHandlerEvent); - } - return; - } - } - if (node.has("code") - && node.has("description") - && "ConfigureFailure".equals(node.path("type").asText())) { - SpeakV2ConfigureFailure configureFailureHandlerEvent = null; - try { - configureFailureHandlerEvent = objectMapper.treeToValue(node, SpeakV2ConfigureFailure.class); - } catch (Exception e) { - } - if (configureFailureHandlerEvent != null) { - if (configureFailureHandler != null) { - configureFailureHandler.accept(configureFailureHandlerEvent); - } - return; - } - } - if (node.has("code") - && node.has("description") - && "Warning".equals(node.path("type").asText())) { - SpeakV2Warning warningHandlerEvent = null; - try { - warningHandlerEvent = objectMapper.treeToValue(node, SpeakV2Warning.class); - } catch (Exception e) { - } - if (warningHandlerEvent != null) { - if (warningHandler != null) { - warningHandler.accept(warningHandlerEvent); - } - return; - } - } - if (node.has("code") - && node.has("description") - && "Error".equals(node.path("type").asText())) { - SpeakV2Error errorHandlerEvent = null; - try { - errorHandlerEvent = objectMapper.treeToValue(node, SpeakV2Error.class); - } catch (Exception e) { - } - if (errorHandlerEvent != null) { - if (errorHandler != null) { - errorHandler.accept(errorHandlerEvent); - } - return; - } - } - if (node.has("speech_id") - && "SpeechStarted".equals(node.path("type").asText())) { - SpeakV2SpeechStarted speechStartedHandlerEvent = null; - try { - speechStartedHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechStarted.class); - } catch (Exception e) { - } - if (speechStartedHandlerEvent != null) { - if (speechStartedHandler != null) { - speechStartedHandler.accept(speechStartedHandlerEvent); - } - return; - } - } - if (node.has("speech_id") && "Flushed".equals(node.path("type").asText())) { - SpeakV2Flushed flushedHandlerEvent = null; - try { - flushedHandlerEvent = objectMapper.treeToValue(node, SpeakV2Flushed.class); - } catch (Exception e) { - } - if (flushedHandlerEvent != null) { - if (flushedHandler != null) { - flushedHandler.accept(flushedHandlerEvent); - } - return; - } - } - if (node.has("applied") - && "ConfigureSuccess".equals(node.path("type").asText())) { - SpeakV2ConfigureSuccess configureSuccessHandlerEvent = null; - try { - configureSuccessHandlerEvent = objectMapper.treeToValue(node, SpeakV2ConfigureSuccess.class); - } catch (Exception e) { - } - if (configureSuccessHandlerEvent != null) { - if (configureSuccessHandler != null) { - configureSuccessHandler.accept(configureSuccessHandlerEvent); - } - return; - } - } - // The raw frame is already delivered to onMessage(String), so ignore unknown typed - // frames for forward compatibility with newly added server control messages. - } catch (Exception e) { - if (onErrorHandler != null) { - onErrorHandler.accept(e); - } - } - } -} diff --git a/src/main/java/com/deepgram/types/DeepgramModel.java b/src/main/java/com/deepgram/types/DeepgramModel.java index fc7c08c..6444d7f 100644 --- a/src/main/java/com/deepgram/types/DeepgramModel.java +++ b/src/main/java/com/deepgram/types/DeepgramModel.java @@ -13,6 +13,8 @@ public final class DeepgramModel { public static final DeepgramModel FLUX_KELSEY_EN = new DeepgramModel(Value.FLUX_KELSEY_EN, "flux-kelsey-en"); + public static final DeepgramModel FLUX_RENEE_EN = new DeepgramModel(Value.FLUX_RENEE_EN, "flux-renee-en"); + public static final DeepgramModel FLUX_SIENNA_EN = new DeepgramModel(Value.FLUX_SIENNA_EN, "flux-sienna-en"); public static final DeepgramModel AURA2HARMONIA_EN = @@ -251,6 +253,8 @@ public T visit(Visitor visitor) { return visitor.visitAura2HeraEn(); case FLUX_KELSEY_EN: return visitor.visitFluxKelseyEn(); + case FLUX_RENEE_EN: + return visitor.visitFluxReneeEn(); case FLUX_SIENNA_EN: return visitor.visitFluxSiennaEn(); case AURA2HARMONIA_EN: @@ -458,6 +462,8 @@ public static DeepgramModel valueOf(String value) { return AURA2HERA_EN; case "flux-kelsey-en": return FLUX_KELSEY_EN; + case "flux-renee-en": + return FLUX_RENEE_EN; case "flux-sienna-en": return FLUX_SIENNA_EN; case "aura-2-harmonia-en": @@ -840,6 +846,8 @@ public enum Value { FLUX_PRIYA_EN, + FLUX_RENEE_EN, + FLUX_RUFUS_EN, FLUX_SEAN_EN, @@ -1042,6 +1050,8 @@ public interface Visitor { T visitFluxPriyaEn(); + T visitFluxReneeEn(); + T visitFluxRufusEn(); T visitFluxSeanEn(); diff --git a/src/main/java/com/deepgram/types/DeepgramModel.java.bak b/src/main/java/com/deepgram/types/DeepgramModel.java.bak deleted file mode 100644 index 14a797c..0000000 --- a/src/main/java/com/deepgram/types/DeepgramModel.java.bak +++ /dev/null @@ -1,1073 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.types; - -// Manual patch - see .fernignore. - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public final class DeepgramModel { - public static final DeepgramModel AURA2SIRIO_ES = new DeepgramModel(Value.AURA2SIRIO_ES, "aura-2-sirio-es"); - - public static final DeepgramModel AURA2HERA_EN = new DeepgramModel(Value.AURA2HERA_EN, "aura-2-hera-en"); - - public static final DeepgramModel FLUX_KELSEY_EN = new DeepgramModel(Value.FLUX_KELSEY_EN, "flux-kelsey-en"); - - public static final DeepgramModel FLUX_RENEE_EN = new DeepgramModel(Value.FLUX_RENEE_EN, "flux-renee-en"); - - public static final DeepgramModel FLUX_SIENNA_EN = new DeepgramModel(Value.FLUX_SIENNA_EN, "flux-sienna-en"); - - public static final DeepgramModel AURA2HARMONIA_EN = - new DeepgramModel(Value.AURA2HARMONIA_EN, "aura-2-harmonia-en"); - - public static final DeepgramModel AURA2CARINA_ES = new DeepgramModel(Value.AURA2CARINA_ES, "aura-2-carina-es"); - - public static final DeepgramModel FLUX_MEENA_EN = new DeepgramModel(Value.FLUX_MEENA_EN, "flux-meena-en"); - - public static final DeepgramModel FLUX_MEGHAN_EN = new DeepgramModel(Value.FLUX_MEGHAN_EN, "flux-meghan-en"); - - public static final DeepgramModel AURA_ZEUS_EN = new DeepgramModel(Value.AURA_ZEUS_EN, "aura-zeus-en"); - - public static final DeepgramModel AURA2HERMES_EN = new DeepgramModel(Value.AURA2HERMES_EN, "aura-2-hermes-en"); - - public static final DeepgramModel FLUX_CONOR_EN = new DeepgramModel(Value.FLUX_CONOR_EN, "flux-conor-en"); - - public static final DeepgramModel AURA2NEPTUNE_EN = new DeepgramModel(Value.AURA2NEPTUNE_EN, "aura-2-neptune-en"); - - public static final DeepgramModel FLUX_BROOKE_EN = new DeepgramModel(Value.FLUX_BROOKE_EN, "flux-brooke-en"); - - public static final DeepgramModel FLUX_RUFUS_EN = new DeepgramModel(Value.FLUX_RUFUS_EN, "flux-rufus-en"); - - public static final DeepgramModel FLUX_WES_EN = new DeepgramModel(Value.FLUX_WES_EN, "flux-wes-en"); - - public static final DeepgramModel AURA2CALLISTA_EN = - new DeepgramModel(Value.AURA2CALLISTA_EN, "aura-2-callista-en"); - - public static final DeepgramModel AURA2AURORA_EN = new DeepgramModel(Value.AURA2AURORA_EN, "aura-2-aurora-en"); - - public static final DeepgramModel AURA2OPHELIA_EN = new DeepgramModel(Value.AURA2OPHELIA_EN, "aura-2-ophelia-en"); - - public static final DeepgramModel FLUX_BRUCE_EN = new DeepgramModel(Value.FLUX_BRUCE_EN, "flux-bruce-en"); - - public static final DeepgramModel AURA2APOLLO_EN = new DeepgramModel(Value.AURA2APOLLO_EN, "aura-2-apollo-en"); - - public static final DeepgramModel AURA2DELIA_EN = new DeepgramModel(Value.AURA2DELIA_EN, "aura-2-delia-en"); - - public static final DeepgramModel AURA2MARS_EN = new DeepgramModel(Value.AURA2MARS_EN, "aura-2-mars-en"); - - public static final DeepgramModel AURA_HERA_EN = new DeepgramModel(Value.AURA_HERA_EN, "aura-hera-en"); - - public static final DeepgramModel FLUX_MAEVE_EN = new DeepgramModel(Value.FLUX_MAEVE_EN, "flux-maeve-en"); - - public static final DeepgramModel FLUX_MARCUS_EN = new DeepgramModel(Value.FLUX_MARCUS_EN, "flux-marcus-en"); - - public static final DeepgramModel AURA2ARIES_EN = new DeepgramModel(Value.AURA2ARIES_EN, "aura-2-aries-en"); - - public static final DeepgramModel FLUX_PAIGE_EN = new DeepgramModel(Value.FLUX_PAIGE_EN, "flux-paige-en"); - - public static final DeepgramModel AURA2JUNO_EN = new DeepgramModel(Value.AURA2JUNO_EN, "aura-2-juno-en"); - - public static final DeepgramModel FLUX_DREW_EN = new DeepgramModel(Value.FLUX_DREW_EN, "flux-drew-en"); - - public static final DeepgramModel AURA2JAVIER_ES = new DeepgramModel(Value.AURA2JAVIER_ES, "aura-2-javier-es"); - - public static final DeepgramModel AURA2ANDROMEDA_EN = - new DeepgramModel(Value.AURA2ANDROMEDA_EN, "aura-2-andromeda-en"); - - public static final DeepgramModel AURA2SATURN_EN = new DeepgramModel(Value.AURA2SATURN_EN, "aura-2-saturn-en"); - - public static final DeepgramModel AURA2IRIS_EN = new DeepgramModel(Value.AURA2IRIS_EN, "aura-2-iris-en"); - - public static final DeepgramModel AURA_ANGUS_EN = new DeepgramModel(Value.AURA_ANGUS_EN, "aura-angus-en"); - - public static final DeepgramModel FLUX_KAI_EN = new DeepgramModel(Value.FLUX_KAI_EN, "flux-kai-en"); - - public static final DeepgramModel FLUX_BRITTANY_EN = new DeepgramModel(Value.FLUX_BRITTANY_EN, "flux-brittany-en"); - - public static final DeepgramModel FLUX_SEAN_EN = new DeepgramModel(Value.FLUX_SEAN_EN, "flux-sean-en"); - - public static final DeepgramModel AURA2HELENA_EN = new DeepgramModel(Value.AURA2HELENA_EN, "aura-2-helena-en"); - - public static final DeepgramModel FLUX_HALEY_EN = new DeepgramModel(Value.FLUX_HALEY_EN, "flux-haley-en"); - - public static final DeepgramModel AURA_STELLA_EN = new DeepgramModel(Value.AURA_STELLA_EN, "aura-stella-en"); - - public static final DeepgramModel FLUX_JACK_EN = new DeepgramModel(Value.FLUX_JACK_EN, "flux-jack-en"); - - public static final DeepgramModel AURA2DRACO_EN = new DeepgramModel(Value.AURA2DRACO_EN, "aura-2-draco-en"); - - public static final DeepgramModel FLUX_PRIYA_EN = new DeepgramModel(Value.FLUX_PRIYA_EN, "flux-priya-en"); - - public static final DeepgramModel AURA_HELIOS_EN = new DeepgramModel(Value.AURA_HELIOS_EN, "aura-helios-en"); - - public static final DeepgramModel AURA2JANUS_EN = new DeepgramModel(Value.AURA2JANUS_EN, "aura-2-janus-en"); - - public static final DeepgramModel AURA2NESTOR_ES = new DeepgramModel(Value.AURA2NESTOR_ES, "aura-2-nestor-es"); - - public static final DeepgramModel AURA2ORION_EN = new DeepgramModel(Value.AURA2ORION_EN, "aura-2-orion-en"); - - public static final DeepgramModel AURA2MINERVA_EN = new DeepgramModel(Value.AURA2MINERVA_EN, "aura-2-minerva-en"); - - public static final DeepgramModel AURA2ALVARO_ES = new DeepgramModel(Value.AURA2ALVARO_ES, "aura-2-alvaro-es"); - - public static final DeepgramModel AURA_PERSEUS_EN = new DeepgramModel(Value.AURA_PERSEUS_EN, "aura-perseus-en"); - - public static final DeepgramModel AURA2VESTA_EN = new DeepgramModel(Value.AURA2VESTA_EN, "aura-2-vesta-en"); - - public static final DeepgramModel FLUX_COLE_EN = new DeepgramModel(Value.FLUX_COLE_EN, "flux-cole-en"); - - public static final DeepgramModel AURA2ZEUS_EN = new DeepgramModel(Value.AURA2ZEUS_EN, "aura-2-zeus-en"); - - public static final DeepgramModel AURA2ORPHEUS_EN = new DeepgramModel(Value.AURA2ORPHEUS_EN, "aura-2-orpheus-en"); - - public static final DeepgramModel AURA2THALIA_EN = new DeepgramModel(Value.AURA2THALIA_EN, "aura-2-thalia-en"); - - public static final DeepgramModel FLUX_NAVEEN_EN = new DeepgramModel(Value.FLUX_NAVEEN_EN, "flux-naveen-en"); - - public static final DeepgramModel AURA2ASTERIA_EN = new DeepgramModel(Value.AURA2ASTERIA_EN, "aura-2-asteria-en"); - - public static final DeepgramModel AURA2SELENA_ES = new DeepgramModel(Value.AURA2SELENA_ES, "aura-2-selena-es"); - - public static final DeepgramModel FLUX_MARCELO_EN = new DeepgramModel(Value.FLUX_MARCELO_EN, "flux-marcelo-en"); - - public static final DeepgramModel FLUX_ELISE_EN = new DeepgramModel(Value.FLUX_ELISE_EN, "flux-elise-en"); - - public static final DeepgramModel FLUX_ALEXIS_EN = new DeepgramModel(Value.FLUX_ALEXIS_EN, "flux-alexis-en"); - - public static final DeepgramModel AURA_LUNA_EN = new DeepgramModel(Value.AURA_LUNA_EN, "aura-luna-en"); - - public static final DeepgramModel AURA_ORPHEUS_EN = new DeepgramModel(Value.AURA_ORPHEUS_EN, "aura-orpheus-en"); - - public static final DeepgramModel AURA2AMALTHEA_EN = - new DeepgramModel(Value.AURA2AMALTHEA_EN, "aura-2-amalthea-en"); - - public static final DeepgramModel FLUX_DONOVAN_EN = new DeepgramModel(Value.FLUX_DONOVAN_EN, "flux-donovan-en"); - - public static final DeepgramModel AURA2SELENE_EN = new DeepgramModel(Value.AURA2SELENE_EN, "aura-2-selene-en"); - - public static final DeepgramModel FLUX_TANNER_EN = new DeepgramModel(Value.FLUX_TANNER_EN, "flux-tanner-en"); - - public static final DeepgramModel FLUX_SHARON_EN = new DeepgramModel(Value.FLUX_SHARON_EN, "flux-sharon-en"); - - public static final DeepgramModel AURA2LUNA_EN = new DeepgramModel(Value.AURA2LUNA_EN, "aura-2-luna-en"); - - public static final DeepgramModel AURA2PHOEBE_EN = new DeepgramModel(Value.AURA2PHOEBE_EN, "aura-2-phoebe-en"); - - public static final DeepgramModel FLUX_HANNAH_EN = new DeepgramModel(Value.FLUX_HANNAH_EN, "flux-hannah-en"); - - public static final DeepgramModel AURA2DIANA_ES = new DeepgramModel(Value.AURA2DIANA_ES, "aura-2-diana-es"); - - public static final DeepgramModel AURA_ORION_EN = new DeepgramModel(Value.AURA_ORION_EN, "aura-orion-en"); - - public static final DeepgramModel AURA2ATHENA_EN = new DeepgramModel(Value.AURA2ATHENA_EN, "aura-2-athena-en"); - - public static final DeepgramModel AURA_ARCAS_EN = new DeepgramModel(Value.AURA_ARCAS_EN, "aura-arcas-en"); - - public static final DeepgramModel AURA2THEIA_EN = new DeepgramModel(Value.AURA2THEIA_EN, "aura-2-theia-en"); - - public static final DeepgramModel FLUX_WADE_EN = new DeepgramModel(Value.FLUX_WADE_EN, "flux-wade-en"); - - public static final DeepgramModel FLUX_HEATHER_EN = new DeepgramModel(Value.FLUX_HEATHER_EN, "flux-heather-en"); - - public static final DeepgramModel FLUX_CLIFF_EN = new DeepgramModel(Value.FLUX_CLIFF_EN, "flux-cliff-en"); - - public static final DeepgramModel AURA2JUPITER_EN = new DeepgramModel(Value.AURA2JUPITER_EN, "aura-2-jupiter-en"); - - public static final DeepgramModel FLUX_KIT_EN = new DeepgramModel(Value.FLUX_KIT_EN, "flux-kit-en"); - - public static final DeepgramModel AURA2AQUILA_ES = new DeepgramModel(Value.AURA2AQUILA_ES, "aura-2-aquila-es"); - - public static final DeepgramModel AURA2CORA_EN = new DeepgramModel(Value.AURA2CORA_EN, "aura-2-cora-en"); - - public static final DeepgramModel AURA2CORDELIA_EN = - new DeepgramModel(Value.AURA2CORDELIA_EN, "aura-2-cordelia-en"); - - public static final DeepgramModel AURA2ATLAS_EN = new DeepgramModel(Value.AURA2ATLAS_EN, "aura-2-atlas-en"); - - public static final DeepgramModel AURA2HYPERION_EN = - new DeepgramModel(Value.AURA2HYPERION_EN, "aura-2-hyperion-en"); - - public static final DeepgramModel AURA2CELESTE_ES = new DeepgramModel(Value.AURA2CELESTE_ES, "aura-2-celeste-es"); - - public static final DeepgramModel AURA2PLUTO_EN = new DeepgramModel(Value.AURA2PLUTO_EN, "aura-2-pluto-en"); - - public static final DeepgramModel FLUX_BREE_EN = new DeepgramModel(Value.FLUX_BREE_EN, "flux-bree-en"); - - public static final DeepgramModel AURA2ARCAS_EN = new DeepgramModel(Value.AURA2ARCAS_EN, "aura-2-arcas-en"); - - public static final DeepgramModel FLUX_COLIN_EN = new DeepgramModel(Value.FLUX_COLIN_EN, "flux-colin-en"); - - public static final DeepgramModel AURA_ATHENA_EN = new DeepgramModel(Value.AURA_ATHENA_EN, "aura-athena-en"); - - public static final DeepgramModel AURA2ODYSSEUS_EN = - new DeepgramModel(Value.AURA2ODYSSEUS_EN, "aura-2-odysseus-en"); - - public static final DeepgramModel AURA2PANDORA_EN = new DeepgramModel(Value.AURA2PANDORA_EN, "aura-2-pandora-en"); - - public static final DeepgramModel FLUX_MILES_EN = new DeepgramModel(Value.FLUX_MILES_EN, "flux-miles-en"); - - public static final DeepgramModel AURA_ASTERIA_EN = new DeepgramModel(Value.AURA_ASTERIA_EN, "aura-asteria-en"); - - public static final DeepgramModel AURA2ELECTRA_EN = new DeepgramModel(Value.AURA2ELECTRA_EN, "aura-2-electra-en"); - - public static final DeepgramModel FLUX_GEMMA_EN = new DeepgramModel(Value.FLUX_GEMMA_EN, "flux-gemma-en"); - - public static final DeepgramModel AURA2ESTRELLA_ES = - new DeepgramModel(Value.AURA2ESTRELLA_ES, "aura-2-estrella-es"); - - private final Value value; - - private final String string; - - DeepgramModel(Value value, String string) { - this.value = value; - this.string = string; - } - - public Value getEnumValue() { - return value; - } - - @java.lang.Override - @JsonValue - public String toString() { - return this.string; - } - - @java.lang.Override - public boolean equals(Object other) { - return (this == other) - || (other instanceof DeepgramModel && this.string.equals(((DeepgramModel) other).string)); - } - - @java.lang.Override - public int hashCode() { - return this.string.hashCode(); - } - - public T visit(Visitor visitor) { - switch (value) { - case AURA2SIRIO_ES: - return visitor.visitAura2SirioEs(); - case AURA2HERA_EN: - return visitor.visitAura2HeraEn(); - case FLUX_KELSEY_EN: - return visitor.visitFluxKelseyEn(); - case FLUX_RENEE_EN: - return visitor.visitFluxReneeEn(); - case FLUX_SIENNA_EN: - return visitor.visitFluxSiennaEn(); - case AURA2HARMONIA_EN: - return visitor.visitAura2HarmoniaEn(); - case AURA2CARINA_ES: - return visitor.visitAura2CarinaEs(); - case FLUX_MEENA_EN: - return visitor.visitFluxMeenaEn(); - case FLUX_MEGHAN_EN: - return visitor.visitFluxMeghanEn(); - case AURA_ZEUS_EN: - return visitor.visitAuraZeusEn(); - case AURA2HERMES_EN: - return visitor.visitAura2HermesEn(); - case FLUX_CONOR_EN: - return visitor.visitFluxConorEn(); - case AURA2NEPTUNE_EN: - return visitor.visitAura2NeptuneEn(); - case FLUX_BROOKE_EN: - return visitor.visitFluxBrookeEn(); - case FLUX_RUFUS_EN: - return visitor.visitFluxRufusEn(); - case FLUX_WES_EN: - return visitor.visitFluxWesEn(); - case AURA2CALLISTA_EN: - return visitor.visitAura2CallistaEn(); - case AURA2AURORA_EN: - return visitor.visitAura2AuroraEn(); - case AURA2OPHELIA_EN: - return visitor.visitAura2OpheliaEn(); - case FLUX_BRUCE_EN: - return visitor.visitFluxBruceEn(); - case AURA2APOLLO_EN: - return visitor.visitAura2ApolloEn(); - case AURA2DELIA_EN: - return visitor.visitAura2DeliaEn(); - case AURA2MARS_EN: - return visitor.visitAura2MarsEn(); - case AURA_HERA_EN: - return visitor.visitAuraHeraEn(); - case FLUX_MAEVE_EN: - return visitor.visitFluxMaeveEn(); - case FLUX_MARCUS_EN: - return visitor.visitFluxMarcusEn(); - case AURA2ARIES_EN: - return visitor.visitAura2AriesEn(); - case FLUX_PAIGE_EN: - return visitor.visitFluxPaigeEn(); - case AURA2JUNO_EN: - return visitor.visitAura2JunoEn(); - case FLUX_DREW_EN: - return visitor.visitFluxDrewEn(); - case AURA2JAVIER_ES: - return visitor.visitAura2JavierEs(); - case AURA2ANDROMEDA_EN: - return visitor.visitAura2AndromedaEn(); - case AURA2SATURN_EN: - return visitor.visitAura2SaturnEn(); - case AURA2IRIS_EN: - return visitor.visitAura2IrisEn(); - case AURA_ANGUS_EN: - return visitor.visitAuraAngusEn(); - case FLUX_KAI_EN: - return visitor.visitFluxKaiEn(); - case FLUX_BRITTANY_EN: - return visitor.visitFluxBrittanyEn(); - case FLUX_SEAN_EN: - return visitor.visitFluxSeanEn(); - case AURA2HELENA_EN: - return visitor.visitAura2HelenaEn(); - case FLUX_HALEY_EN: - return visitor.visitFluxHaleyEn(); - case AURA_STELLA_EN: - return visitor.visitAuraStellaEn(); - case FLUX_JACK_EN: - return visitor.visitFluxJackEn(); - case AURA2DRACO_EN: - return visitor.visitAura2DracoEn(); - case FLUX_PRIYA_EN: - return visitor.visitFluxPriyaEn(); - case AURA_HELIOS_EN: - return visitor.visitAuraHeliosEn(); - case AURA2JANUS_EN: - return visitor.visitAura2JanusEn(); - case AURA2NESTOR_ES: - return visitor.visitAura2NestorEs(); - case AURA2ORION_EN: - return visitor.visitAura2OrionEn(); - case AURA2MINERVA_EN: - return visitor.visitAura2MinervaEn(); - case AURA2ALVARO_ES: - return visitor.visitAura2AlvaroEs(); - case AURA_PERSEUS_EN: - return visitor.visitAuraPerseusEn(); - case AURA2VESTA_EN: - return visitor.visitAura2VestaEn(); - case FLUX_COLE_EN: - return visitor.visitFluxColeEn(); - case AURA2ZEUS_EN: - return visitor.visitAura2ZeusEn(); - case AURA2ORPHEUS_EN: - return visitor.visitAura2OrpheusEn(); - case AURA2THALIA_EN: - return visitor.visitAura2ThaliaEn(); - case FLUX_NAVEEN_EN: - return visitor.visitFluxNaveenEn(); - case AURA2ASTERIA_EN: - return visitor.visitAura2AsteriaEn(); - case AURA2SELENA_ES: - return visitor.visitAura2SelenaEs(); - case FLUX_MARCELO_EN: - return visitor.visitFluxMarceloEn(); - case FLUX_ELISE_EN: - return visitor.visitFluxEliseEn(); - case FLUX_ALEXIS_EN: - return visitor.visitFluxAlexisEn(); - case AURA_LUNA_EN: - return visitor.visitAuraLunaEn(); - case AURA_ORPHEUS_EN: - return visitor.visitAuraOrpheusEn(); - case AURA2AMALTHEA_EN: - return visitor.visitAura2AmaltheaEn(); - case FLUX_DONOVAN_EN: - return visitor.visitFluxDonovanEn(); - case AURA2SELENE_EN: - return visitor.visitAura2SeleneEn(); - case FLUX_TANNER_EN: - return visitor.visitFluxTannerEn(); - case FLUX_SHARON_EN: - return visitor.visitFluxSharonEn(); - case AURA2LUNA_EN: - return visitor.visitAura2LunaEn(); - case AURA2PHOEBE_EN: - return visitor.visitAura2PhoebeEn(); - case FLUX_HANNAH_EN: - return visitor.visitFluxHannahEn(); - case AURA2DIANA_ES: - return visitor.visitAura2DianaEs(); - case AURA_ORION_EN: - return visitor.visitAuraOrionEn(); - case AURA2ATHENA_EN: - return visitor.visitAura2AthenaEn(); - case AURA_ARCAS_EN: - return visitor.visitAuraArcasEn(); - case AURA2THEIA_EN: - return visitor.visitAura2TheiaEn(); - case FLUX_WADE_EN: - return visitor.visitFluxWadeEn(); - case FLUX_HEATHER_EN: - return visitor.visitFluxHeatherEn(); - case FLUX_CLIFF_EN: - return visitor.visitFluxCliffEn(); - case AURA2JUPITER_EN: - return visitor.visitAura2JupiterEn(); - case FLUX_KIT_EN: - return visitor.visitFluxKitEn(); - case AURA2AQUILA_ES: - return visitor.visitAura2AquilaEs(); - case AURA2CORA_EN: - return visitor.visitAura2CoraEn(); - case AURA2CORDELIA_EN: - return visitor.visitAura2CordeliaEn(); - case AURA2ATLAS_EN: - return visitor.visitAura2AtlasEn(); - case AURA2HYPERION_EN: - return visitor.visitAura2HyperionEn(); - case AURA2CELESTE_ES: - return visitor.visitAura2CelesteEs(); - case AURA2PLUTO_EN: - return visitor.visitAura2PlutoEn(); - case FLUX_BREE_EN: - return visitor.visitFluxBreeEn(); - case AURA2ARCAS_EN: - return visitor.visitAura2ArcasEn(); - case FLUX_COLIN_EN: - return visitor.visitFluxColinEn(); - case AURA_ATHENA_EN: - return visitor.visitAuraAthenaEn(); - case AURA2ODYSSEUS_EN: - return visitor.visitAura2OdysseusEn(); - case AURA2PANDORA_EN: - return visitor.visitAura2PandoraEn(); - case FLUX_MILES_EN: - return visitor.visitFluxMilesEn(); - case AURA_ASTERIA_EN: - return visitor.visitAuraAsteriaEn(); - case AURA2ELECTRA_EN: - return visitor.visitAura2ElectraEn(); - case FLUX_GEMMA_EN: - return visitor.visitFluxGemmaEn(); - case AURA2ESTRELLA_ES: - return visitor.visitAura2EstrellaEs(); - case UNKNOWN: - default: - return visitor.visitUnknown(string); - } - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static DeepgramModel valueOf(String value) { - switch (value) { - case "aura-2-sirio-es": - return AURA2SIRIO_ES; - case "aura-2-hera-en": - return AURA2HERA_EN; - case "flux-kelsey-en": - return FLUX_KELSEY_EN; - case "flux-renee-en": - return FLUX_RENEE_EN; - case "flux-sienna-en": - return FLUX_SIENNA_EN; - case "aura-2-harmonia-en": - return AURA2HARMONIA_EN; - case "aura-2-carina-es": - return AURA2CARINA_ES; - case "flux-meena-en": - return FLUX_MEENA_EN; - case "flux-meghan-en": - return FLUX_MEGHAN_EN; - case "aura-zeus-en": - return AURA_ZEUS_EN; - case "aura-2-hermes-en": - return AURA2HERMES_EN; - case "flux-conor-en": - return FLUX_CONOR_EN; - case "aura-2-neptune-en": - return AURA2NEPTUNE_EN; - case "flux-brooke-en": - return FLUX_BROOKE_EN; - case "flux-rufus-en": - return FLUX_RUFUS_EN; - case "flux-wes-en": - return FLUX_WES_EN; - case "aura-2-callista-en": - return AURA2CALLISTA_EN; - case "aura-2-aurora-en": - return AURA2AURORA_EN; - case "aura-2-ophelia-en": - return AURA2OPHELIA_EN; - case "flux-bruce-en": - return FLUX_BRUCE_EN; - case "aura-2-apollo-en": - return AURA2APOLLO_EN; - case "aura-2-delia-en": - return AURA2DELIA_EN; - case "aura-2-mars-en": - return AURA2MARS_EN; - case "aura-hera-en": - return AURA_HERA_EN; - case "flux-maeve-en": - return FLUX_MAEVE_EN; - case "flux-marcus-en": - return FLUX_MARCUS_EN; - case "aura-2-aries-en": - return AURA2ARIES_EN; - case "flux-paige-en": - return FLUX_PAIGE_EN; - case "aura-2-juno-en": - return AURA2JUNO_EN; - case "flux-drew-en": - return FLUX_DREW_EN; - case "aura-2-javier-es": - return AURA2JAVIER_ES; - case "aura-2-andromeda-en": - return AURA2ANDROMEDA_EN; - case "aura-2-saturn-en": - return AURA2SATURN_EN; - case "aura-2-iris-en": - return AURA2IRIS_EN; - case "aura-angus-en": - return AURA_ANGUS_EN; - case "flux-kai-en": - return FLUX_KAI_EN; - case "flux-brittany-en": - return FLUX_BRITTANY_EN; - case "flux-sean-en": - return FLUX_SEAN_EN; - case "aura-2-helena-en": - return AURA2HELENA_EN; - case "flux-haley-en": - return FLUX_HALEY_EN; - case "aura-stella-en": - return AURA_STELLA_EN; - case "flux-jack-en": - return FLUX_JACK_EN; - case "aura-2-draco-en": - return AURA2DRACO_EN; - case "flux-priya-en": - return FLUX_PRIYA_EN; - case "aura-helios-en": - return AURA_HELIOS_EN; - case "aura-2-janus-en": - return AURA2JANUS_EN; - case "aura-2-nestor-es": - return AURA2NESTOR_ES; - case "aura-2-orion-en": - return AURA2ORION_EN; - case "aura-2-minerva-en": - return AURA2MINERVA_EN; - case "aura-2-alvaro-es": - return AURA2ALVARO_ES; - case "aura-perseus-en": - return AURA_PERSEUS_EN; - case "aura-2-vesta-en": - return AURA2VESTA_EN; - case "flux-cole-en": - return FLUX_COLE_EN; - case "aura-2-zeus-en": - return AURA2ZEUS_EN; - case "aura-2-orpheus-en": - return AURA2ORPHEUS_EN; - case "aura-2-thalia-en": - return AURA2THALIA_EN; - case "flux-naveen-en": - return FLUX_NAVEEN_EN; - case "aura-2-asteria-en": - return AURA2ASTERIA_EN; - case "aura-2-selena-es": - return AURA2SELENA_ES; - case "flux-marcelo-en": - return FLUX_MARCELO_EN; - case "flux-elise-en": - return FLUX_ELISE_EN; - case "flux-alexis-en": - return FLUX_ALEXIS_EN; - case "aura-luna-en": - return AURA_LUNA_EN; - case "aura-orpheus-en": - return AURA_ORPHEUS_EN; - case "aura-2-amalthea-en": - return AURA2AMALTHEA_EN; - case "flux-donovan-en": - return FLUX_DONOVAN_EN; - case "aura-2-selene-en": - return AURA2SELENE_EN; - case "flux-tanner-en": - return FLUX_TANNER_EN; - case "flux-sharon-en": - return FLUX_SHARON_EN; - case "aura-2-luna-en": - return AURA2LUNA_EN; - case "aura-2-phoebe-en": - return AURA2PHOEBE_EN; - case "flux-hannah-en": - return FLUX_HANNAH_EN; - case "aura-2-diana-es": - return AURA2DIANA_ES; - case "aura-orion-en": - return AURA_ORION_EN; - case "aura-2-athena-en": - return AURA2ATHENA_EN; - case "aura-arcas-en": - return AURA_ARCAS_EN; - case "aura-2-theia-en": - return AURA2THEIA_EN; - case "flux-wade-en": - return FLUX_WADE_EN; - case "flux-heather-en": - return FLUX_HEATHER_EN; - case "flux-cliff-en": - return FLUX_CLIFF_EN; - case "aura-2-jupiter-en": - return AURA2JUPITER_EN; - case "flux-kit-en": - return FLUX_KIT_EN; - case "aura-2-aquila-es": - return AURA2AQUILA_ES; - case "aura-2-cora-en": - return AURA2CORA_EN; - case "aura-2-cordelia-en": - return AURA2CORDELIA_EN; - case "aura-2-atlas-en": - return AURA2ATLAS_EN; - case "aura-2-hyperion-en": - return AURA2HYPERION_EN; - case "aura-2-celeste-es": - return AURA2CELESTE_ES; - case "aura-2-pluto-en": - return AURA2PLUTO_EN; - case "flux-bree-en": - return FLUX_BREE_EN; - case "aura-2-arcas-en": - return AURA2ARCAS_EN; - case "flux-colin-en": - return FLUX_COLIN_EN; - case "aura-athena-en": - return AURA_ATHENA_EN; - case "aura-2-odysseus-en": - return AURA2ODYSSEUS_EN; - case "aura-2-pandora-en": - return AURA2PANDORA_EN; - case "flux-miles-en": - return FLUX_MILES_EN; - case "aura-asteria-en": - return AURA_ASTERIA_EN; - case "aura-2-electra-en": - return AURA2ELECTRA_EN; - case "flux-gemma-en": - return FLUX_GEMMA_EN; - case "aura-2-estrella-es": - return AURA2ESTRELLA_ES; - default: - return new DeepgramModel(Value.UNKNOWN, value); - } - } - - public enum Value { - AURA_ASTERIA_EN, - - AURA_LUNA_EN, - - AURA_STELLA_EN, - - AURA_ATHENA_EN, - - AURA_HERA_EN, - - AURA_ORION_EN, - - AURA_ARCAS_EN, - - AURA_PERSEUS_EN, - - AURA_ANGUS_EN, - - AURA_ORPHEUS_EN, - - AURA_HELIOS_EN, - - AURA_ZEUS_EN, - - AURA2AMALTHEA_EN, - - AURA2ANDROMEDA_EN, - - AURA2APOLLO_EN, - - AURA2ARCAS_EN, - - AURA2ARIES_EN, - - AURA2ASTERIA_EN, - - AURA2ATHENA_EN, - - AURA2ATLAS_EN, - - AURA2AURORA_EN, - - AURA2CALLISTA_EN, - - AURA2CORA_EN, - - AURA2CORDELIA_EN, - - AURA2DELIA_EN, - - AURA2DRACO_EN, - - AURA2ELECTRA_EN, - - AURA2HARMONIA_EN, - - AURA2HELENA_EN, - - AURA2HERA_EN, - - AURA2HERMES_EN, - - AURA2HYPERION_EN, - - AURA2IRIS_EN, - - AURA2JANUS_EN, - - AURA2JUNO_EN, - - AURA2JUPITER_EN, - - AURA2LUNA_EN, - - AURA2MARS_EN, - - AURA2MINERVA_EN, - - AURA2NEPTUNE_EN, - - AURA2ODYSSEUS_EN, - - AURA2OPHELIA_EN, - - AURA2ORION_EN, - - AURA2ORPHEUS_EN, - - AURA2PANDORA_EN, - - AURA2PHOEBE_EN, - - AURA2PLUTO_EN, - - AURA2SATURN_EN, - - AURA2SELENE_EN, - - AURA2THALIA_EN, - - AURA2THEIA_EN, - - AURA2VESTA_EN, - - AURA2ZEUS_EN, - - AURA2SIRIO_ES, - - AURA2NESTOR_ES, - - AURA2CARINA_ES, - - AURA2CELESTE_ES, - - AURA2ALVARO_ES, - - AURA2DIANA_ES, - - AURA2AQUILA_ES, - - AURA2SELENA_ES, - - AURA2ESTRELLA_ES, - - AURA2JAVIER_ES, - - FLUX_ALEXIS_EN, - - FLUX_BREE_EN, - - FLUX_BRITTANY_EN, - - FLUX_BROOKE_EN, - - FLUX_BRUCE_EN, - - FLUX_CLIFF_EN, - - FLUX_COLE_EN, - - FLUX_COLIN_EN, - - FLUX_CONOR_EN, - - FLUX_DONOVAN_EN, - - FLUX_DREW_EN, - - FLUX_ELISE_EN, - - FLUX_GEMMA_EN, - - FLUX_HALEY_EN, - - FLUX_HANNAH_EN, - - FLUX_HEATHER_EN, - - FLUX_JACK_EN, - - FLUX_KAI_EN, - - FLUX_KELSEY_EN, - - FLUX_KIT_EN, - - FLUX_MAEVE_EN, - - FLUX_MARCELO_EN, - - FLUX_MARCUS_EN, - - FLUX_MEENA_EN, - - FLUX_MEGHAN_EN, - - FLUX_MILES_EN, - - FLUX_NAVEEN_EN, - - FLUX_PAIGE_EN, - - FLUX_PRIYA_EN, - - FLUX_RENEE_EN, - - FLUX_RUFUS_EN, - - FLUX_SEAN_EN, - - FLUX_SHARON_EN, - - FLUX_SIENNA_EN, - - FLUX_TANNER_EN, - - FLUX_WADE_EN, - - FLUX_WES_EN, - - UNKNOWN - } - - public interface Visitor { - T visitAuraAsteriaEn(); - - T visitAuraLunaEn(); - - T visitAuraStellaEn(); - - T visitAuraAthenaEn(); - - T visitAuraHeraEn(); - - T visitAuraOrionEn(); - - T visitAuraArcasEn(); - - T visitAuraPerseusEn(); - - T visitAuraAngusEn(); - - T visitAuraOrpheusEn(); - - T visitAuraHeliosEn(); - - T visitAuraZeusEn(); - - T visitAura2AmaltheaEn(); - - T visitAura2AndromedaEn(); - - T visitAura2ApolloEn(); - - T visitAura2ArcasEn(); - - T visitAura2AriesEn(); - - T visitAura2AsteriaEn(); - - T visitAura2AthenaEn(); - - T visitAura2AtlasEn(); - - T visitAura2AuroraEn(); - - T visitAura2CallistaEn(); - - T visitAura2CoraEn(); - - T visitAura2CordeliaEn(); - - T visitAura2DeliaEn(); - - T visitAura2DracoEn(); - - T visitAura2ElectraEn(); - - T visitAura2HarmoniaEn(); - - T visitAura2HelenaEn(); - - T visitAura2HeraEn(); - - T visitAura2HermesEn(); - - T visitAura2HyperionEn(); - - T visitAura2IrisEn(); - - T visitAura2JanusEn(); - - T visitAura2JunoEn(); - - T visitAura2JupiterEn(); - - T visitAura2LunaEn(); - - T visitAura2MarsEn(); - - T visitAura2MinervaEn(); - - T visitAura2NeptuneEn(); - - T visitAura2OdysseusEn(); - - T visitAura2OpheliaEn(); - - T visitAura2OrionEn(); - - T visitAura2OrpheusEn(); - - T visitAura2PandoraEn(); - - T visitAura2PhoebeEn(); - - T visitAura2PlutoEn(); - - T visitAura2SaturnEn(); - - T visitAura2SeleneEn(); - - T visitAura2ThaliaEn(); - - T visitAura2TheiaEn(); - - T visitAura2VestaEn(); - - T visitAura2ZeusEn(); - - T visitAura2SirioEs(); - - T visitAura2NestorEs(); - - T visitAura2CarinaEs(); - - T visitAura2CelesteEs(); - - T visitAura2AlvaroEs(); - - T visitAura2DianaEs(); - - T visitAura2AquilaEs(); - - T visitAura2SelenaEs(); - - T visitAura2EstrellaEs(); - - T visitAura2JavierEs(); - - T visitFluxAlexisEn(); - - T visitFluxBreeEn(); - - T visitFluxBrittanyEn(); - - T visitFluxBrookeEn(); - - T visitFluxBruceEn(); - - T visitFluxCliffEn(); - - T visitFluxColeEn(); - - T visitFluxColinEn(); - - T visitFluxConorEn(); - - T visitFluxDonovanEn(); - - T visitFluxDrewEn(); - - T visitFluxEliseEn(); - - T visitFluxGemmaEn(); - - T visitFluxHaleyEn(); - - T visitFluxHannahEn(); - - T visitFluxHeatherEn(); - - T visitFluxJackEn(); - - T visitFluxKaiEn(); - - T visitFluxKelseyEn(); - - T visitFluxKitEn(); - - T visitFluxMaeveEn(); - - T visitFluxMarceloEn(); - - T visitFluxMarcusEn(); - - T visitFluxMeenaEn(); - - T visitFluxMeghanEn(); - - T visitFluxMilesEn(); - - T visitFluxNaveenEn(); - - T visitFluxPaigeEn(); - - T visitFluxPriyaEn(); - - T visitFluxReneeEn(); - - T visitFluxRufusEn(); - - T visitFluxSeanEn(); - - T visitFluxSharonEn(); - - T visitFluxSiennaEn(); - - T visitFluxTannerEn(); - - T visitFluxWadeEn(); - - T visitFluxWesEn(); - - T visitUnknown(String unknownType); - } -} From b9a4163a5dfcba30a1c63042b64c86a2e2357f3f Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 13:56:22 +0100 Subject: [PATCH 05/14] test: cover Flux TTS controls --- docs/Migrating-v0.10-to-v0.11.md | 87 ++++++++++++ .../java/com/deepgram/IntegrationTest.java | 129 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 docs/Migrating-v0.10-to-v0.11.md diff --git a/docs/Migrating-v0.10-to-v0.11.md b/docs/Migrating-v0.10-to-v0.11.md new file mode 100644 index 0000000..4d7cce0 --- /dev/null +++ b/docs/Migrating-v0.10-to-v0.11.md @@ -0,0 +1,87 @@ +# v0.10 to v0.11 Migration Guide + +This guide covers the breaking source changes in Deepgram Java SDK `0.11.0`. The release corrects the shared Topics and Intents response shape, adds a Speak V2 Configure failure code, and includes Fern retry behavior updates. It also adds Flux TTS Controls support. + +## Update the dependency + +Upgrade to `0.11.0` with Gradle or Maven. + +**Gradle** + +```groovy +dependencies { + implementation 'com.deepgram:deepgram-java-sdk:0.11.0' +} +``` + +**Maven** + +```xml + + com.deepgram + deepgram-java-sdk + 0.11.0 + +``` + +## Topics and Intents segments + +`SharedTopics` and `SharedIntents` now expose their segments directly. The previous generated models reflected an extra `results.topics` or `results.intents` wrapper that is not present in the API payload. + +| Old path | New path | +| --- | --- | +| `SharedTopics.getResults().get().getTopics().get().getSegments()` | `SharedTopics.getSegments()` | +| `SharedIntents.getResults().get().getIntents().get().getSegments()` | `SharedIntents.getSegments()` | +| `com.deepgram.types.SharedTopicsResultsTopicsSegmentsItem` | `com.deepgram.types.SharedTopicsSegmentsItem` | +| `com.deepgram.types.SharedIntentsResultsIntentsSegmentsItem` | `com.deepgram.types.SharedIntentsSegmentsItem` | + +For example, replace the old Topics traversal: + +```java +List segments = topics.getResults().get() + .getTopics().get() + .getSegments().get(); +``` + +with: + +```java +List segments = topics.getSegments().get(); +``` + +Make the equivalent replacement for `SharedIntents` and its segment item type. + +## Speak V2 Configure failure visitor + +`SpeakV2ConfigureFailureCode` now includes `CONTROL_COMBINATION_INVALID`. Implementations of `SpeakV2ConfigureFailureCode.Visitor` must add `visitControlCombinationInvalid()`. + +```java +SpeakV2ConfigureFailureCode.Visitor visitor = new SpeakV2ConfigureFailureCode.Visitor<>() { + @Override + public String visitControlCombinationInvalid() { + return "Change the conflicting Controls configuration."; + } + + // Implement the remaining visitor methods. +}; +``` + +## Retry behavior + +Fern `4.21.2` and `4.22.2` change retry behavior without changing the public client-builder API. + +- The configured HTTP timeout is now applied to each attempt, rather than to the full retry loop. A request can therefore take longer than its configured timeout when retries and backoff occur. +- Before retrying a retryable HTTP response, the SDK buffers that response. If a later retry fails with a transport error, the SDK returns the earlier HTTP response instead of discarding its status, headers, and body. + +The tradeoffs are intentional: requests can occupy a caller for longer, and buffering holds a retryable response body in memory, but callers retain the actionable API error that triggered the retry instead of only observing a later connection failure. Set explicit timeouts and retry limits appropriate for latency-sensitive or large-response workloads, and continue to inspect returned HTTP errors during incident handling. + +## Flux TTS Controls + +Flux TTS now supports inline Controls for speed, pauses, and pronunciation overrides. Controls are additive: batch requests use `SpeakV2Request.text(...)`, while streaming requests use `SpeakV2Speak.text(...)`. + +```java +String pause = "\\{pause:500ms\\}"; +String pronunciation = "\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}"; +``` + +Pronunciation controls are Early Access. A pronunciation control cannot be combined with a non-default speed or with a pause; the API rejects those combinations with `CONTROL_COMBINATION_INVALID`. See [Flux TTS Controls](https://developers.deepgram.com/docs/tts-voice-controls) for supported syntax and availability. diff --git a/src/test/java/com/deepgram/IntegrationTest.java b/src/test/java/com/deepgram/IntegrationTest.java index a6c2ef1..3148d82 100644 --- a/src/test/java/com/deepgram/IntegrationTest.java +++ b/src/test/java/com/deepgram/IntegrationTest.java @@ -14,9 +14,13 @@ import com.deepgram.resources.listen.v2.types.ListenV2TurnInfoEvent; import com.deepgram.resources.read.v1.text.requests.TextAnalyzeRequest; import com.deepgram.resources.speak.v1.audio.requests.SpeakV1Request; +import com.deepgram.resources.speak.v2.audio.requests.SpeakV2Request; +import com.deepgram.resources.speak.v2.audio.types.AudioGenerateRequestEncoding; import com.deepgram.resources.speak.v2.types.SpeakV2Close; import com.deepgram.resources.speak.v2.types.SpeakV2Flush; import com.deepgram.resources.speak.v2.types.SpeakV2Speak; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechMetadata; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechMetadataControlsApplied; import com.deepgram.resources.speak.v2.websocket.V2ConnectOptions; import com.deepgram.resources.speak.v2.websocket.V2WebSocketClient; import com.deepgram.types.ListProjectsV1Response; @@ -90,6 +94,14 @@ void setUp() { client = builder.build(); } + private void assumeFluxTtsControlsEnabled() { + // Controls are Early Access, so deployments opt in until production availability is universal. + String enabled = System.getenv("DEEPGRAM_FLUX_TTS_CONTROLS"); + org.junit.jupiter.api.Assumptions.assumeTrue( + enabled != null && !enabled.isEmpty(), + "DEEPGRAM_FLUX_TTS_CONTROLS not set, skipping Flux TTS Controls integration test"); + } + // --- Tier 1: Must pass before release --- @Nested @@ -330,6 +342,123 @@ void testIntegration_SpeakV2WebSocket() throws Exception { } } + @Test + @DisplayName("SpeakV2ControlsBatchPause - synthesize batch audio with a pause control") + void testIntegration_SpeakV2ControlsBatchPause() throws Exception { + assumeFluxTtsControlsEnabled(); + + SpeakV2Request request = SpeakV2Request.builder() + .model("flux-alexis-en") + .text("Before \\{pause:500ms\\} after.") + .encoding(AudioGenerateRequestEncoding.MP3) + .build(); + + try (InputStream audio = client.speak().v2().audio().generate(request)) { + assertThat(audio.readAllBytes()).as("expected audio bytes").isNotEmpty(); + } + } + + @Test + @DisplayName("SpeakV2ControlsBatchPronunciation - synthesize batch audio with a pronunciation control") + void testIntegration_SpeakV2ControlsBatchPronunciation() throws Exception { + assumeFluxTtsControlsEnabled(); + + SpeakV2Request request = SpeakV2Request.builder() + .model("flux-alexis-en") + .text("\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}") + .encoding(AudioGenerateRequestEncoding.MP3) + .build(); + + try (InputStream audio = client.speak().v2().audio().generate(request)) { + assertThat(audio.readAllBytes()).as("expected audio bytes").isNotEmpty(); + } + } + + @Test + @DisplayName("SpeakV2ControlsBatchConflict - reject speed with a pronunciation control") + void testIntegration_SpeakV2ControlsBatchConflict() { + assumeFluxTtsControlsEnabled(); + + SpeakV2Request request = SpeakV2Request.builder() + .model("flux-alexis-en") + .text("\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}") + .encoding(AudioGenerateRequestEncoding.MP3) + .speed(1.05) + .build(); + + assertThatThrownBy(() -> client.speak().v2().audio().generate(request)) + .isInstanceOf(DeepgramHttpException.class) + .satisfies(thrown -> { + DeepgramHttpException exception = (DeepgramHttpException) thrown; + assertThat(exception.statusCode()).isEqualTo(400); + assertThat(String.valueOf(exception.body())).contains("CONTROL_COMBINATION_INVALID"); + }); + } + + @Test + @DisplayName("SpeakV2ControlsWebSocket - stream audio and metadata with a pronunciation control") + void testIntegration_SpeakV2ControlsWebSocket() throws Exception { + assumeFluxTtsControlsEnabled(); + + V2WebSocketClient wsClient = client.speak().v2().v2WebSocket(); + CountDownLatch audioLatch = new CountDownLatch(1); + CountDownLatch metadataLatch = new CountDownLatch(1); + AtomicLong totalAudioBytes = new AtomicLong(0); + AtomicReference metadata = new AtomicReference<>(); + AtomicReference serverError = new AtomicReference<>(); + + wsClient.onSpeakV2Audio(audio -> { + totalAudioBytes.addAndGet(audio.size()); + audioLatch.countDown(); + }); + wsClient.onSpeechMetadata(value -> { + metadata.set(value); + metadataLatch.countDown(); + }); + wsClient.onErrorMessage(error -> serverError.set(String.valueOf(error))); + wsClient.onError(error -> serverError.set(error.getMessage())); + + try { + wsClient.connect(V2ConnectOptions.builder() + .model("flux-alexis-en") + .build()) + .get(15, TimeUnit.SECONDS); + wsClient.sendSpeak(SpeakV2Speak.builder() + .text("\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}") + .build()); + wsClient.sendFlush(SpeakV2Flush.builder().build()); + + assertThat(audioLatch.await(20, TimeUnit.SECONDS)) + .as("received audio") + .isTrue(); + assertThat(metadataLatch.await(20, TimeUnit.SECONDS)) + .as("received SpeechMetadata") + .isTrue(); + + assertThat(serverError.get()) + .as("no server/transport error during streaming") + .isNull(); + assertThat(totalAudioBytes.get()) + .as("expected streamed audio bytes") + .isGreaterThan(0); + + SpeakV2SpeechMetadata speechMetadata = metadata.get(); + assertThat(speechMetadata).as("received SpeechMetadata").isNotNull(); + SpeakV2SpeechMetadataControlsApplied controlsApplied = speechMetadata.getControlsApplied(); + assertThat(controlsApplied) + .as("received controls_applied metadata") + .isNotNull(); + // Pronunciation is Early Access; verify a valid report without requiring a non-zero count. + assertThat(controlsApplied.getPronunciationsApplied()).isGreaterThanOrEqualTo(0); + assertThat(controlsApplied.getBreaksApplied()).isGreaterThanOrEqualTo(0); + assertThat(controlsApplied.getPronunciationWarnings()).isGreaterThanOrEqualTo(0); + + wsClient.sendClose(SpeakV2Close.builder().build()); + } finally { + wsClient.disconnect(); + } + } + @Test @DisplayName("ListenV2ForceEndTurn - force a turn to end and read back trigger=manual") void testIntegration_ListenV2ForceEndTurn() throws Exception { From 6d46149d8995a310ebce7dd32d5dfcae645d11d0 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 14:27:52 +0100 Subject: [PATCH 06/14] docs: add Flux TTS Controls example --- examples/speak/FluxTtsControls.java | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 examples/speak/FluxTtsControls.java diff --git a/examples/speak/FluxTtsControls.java b/examples/speak/FluxTtsControls.java new file mode 100644 index 0000000..333c980 --- /dev/null +++ b/examples/speak/FluxTtsControls.java @@ -0,0 +1,59 @@ +import com.deepgram.DeepgramClient; +import com.deepgram.resources.speak.v2.audio.requests.SpeakV2Request; +import com.deepgram.resources.speak.v2.audio.types.AudioGenerateRequestEncoding; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +/** + * Generates Flux TTS batch audio with pause and pronunciation controls. + * + *

Usage: {@code java FluxTtsControls [output-directory]} + * + *

Pause and pronunciation controls cannot be combined in the same Flux TTS request. This example sends them + * separately. + */ +public class FluxTtsControls { + private static final String MODEL = "flux-alexis-en"; + + public static void main(String[] args) { + String apiKey = System.getenv("DEEPGRAM_API_KEY"); + if (apiKey == null || apiKey.isEmpty()) { + System.err.println("DEEPGRAM_API_KEY environment variable is required"); + System.exit(1); + } + + Path outputDirectory = args.length == 1 ? Path.of(args[0]) : Path.of("."); + + try (DeepgramClient client = DeepgramClient.builder().apiKey(apiKey).build()) { + Files.createDirectories(outputDirectory); + + generate( + client, + "Your confirmation number is four seven two. \\{pause:500ms\\} Is there anything else I can help with?", + outputDirectory.resolve("flux-pause.mp3")); + generate( + client, + "Take \\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\} twice daily.", + outputDirectory.resolve("flux-pronunciation.mp3")); + } catch (Exception e) { + System.err.println("Error generating Flux TTS Controls audio: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + private static void generate(DeepgramClient client, String text, Path outputPath) throws Exception { + SpeakV2Request request = SpeakV2Request.builder() + .model(MODEL) + .text(text) + .encoding(AudioGenerateRequestEncoding.MP3) + .build(); + + try (InputStream audio = client.speak().v2().audio().generate(request)) { + long bytes = Files.copy(audio, outputPath, StandardCopyOption.REPLACE_EXISTING); + System.out.printf("Audio saved to %s (%d bytes)%n", outputPath, bytes); + } + } +} From eefcde44c3e34fcc5fe47ae804303774d975275c Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 14:31:21 +0100 Subject: [PATCH 07/14] chore: align regeneration guidance --- .fernignore | 3 ++- AGENTS.md | 3 +-- README.md | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.fernignore b/.fernignore index 918d871..ef6a835 100644 --- a/.fernignore +++ b/.fernignore @@ -14,7 +14,8 @@ src/main/java/com/deepgram/AsyncDeepgramClientBuilder.java # Contains User-Agent, X-Fern-SDK-Name, and X-Fern-SDK-Version headers # with // x-release-please-version comments for automated version bumps. # Fern regen overwrites these with incorrect SDK names and strips the markers. -# Fern 4.21.0 owns the OkHttp lifecycle behavior; retain it while reapplying only these headers. +# Fern 4.22.1 closes tracked WebSockets before SDK-owned OkHttp resources; retain that lifecycle behavior while +# reapplying only these headers. src/main/java/com/deepgram/core/ClientOptions.java # Transport abstraction (pluggable transport for SageMaker, etc.) diff --git a/AGENTS.md b/AGENTS.md index 7c2f867..bcef6fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,11 +47,10 @@ How to identify: Current temporarily frozen files: -- `src/main/java/com/deepgram/core/ClientOptions.java` - preserves release-please version markers and correct SDK header constants that Fern currently overwrites; use the standard `.bak` swap/restore workflow during regen review. Since generator 4.18.0 Fern emits a `getSdkVersion()` helper reading `Package.getImplementationVersion()` instead of a literal. That *does* resolve in the published artifact (CI publishes via `mvn deploy -P release`, and `pom.xml`'s maven-jar-plugin sets `addDefaultImplementationEntries=true`, so the JAR manifest carries `Implementation-Version`), but it resolves to `null` under Gradle and in tests, where it silently falls back to a hardcoded literal the generator does not keep current. We keep the explicit literals because they are correct in every context and because `.github/release-please-config.json` already lists this file in `extra-files`, so release-please bumps it alongside `pom.xml`, `build.gradle`, and `.fern/metadata.json`. Fern also emits `User-Agent` with a `com.deepgram.` prefix while leaving `X-Fern-SDK-Name` on the `com.deepgram:` Maven-coordinate form; we keep both on the colon form. Fern 4.21.0 generates `AutoCloseable` root clients and ownership-aware OkHttp cleanup; the permanent custom client wrappers inherit it, so do not add wrapper-local lifecycle code and retain Fern's lifecycle implementation during reconciliation. +- `src/main/java/com/deepgram/core/ClientOptions.java` - preserves release-please version markers and correct SDK header constants that Fern currently overwrites; use the standard `.bak` swap/restore workflow during regen review. Since generator 4.18.0 Fern emits a `getSdkVersion()` helper reading `Package.getImplementationVersion()` instead of a literal. That *does* resolve in the published artifact (CI publishes via `mvn deploy -P release`, and `pom.xml`'s maven-jar-plugin sets `addDefaultImplementationEntries=true`, so the JAR manifest carries `Implementation-Version`), but it resolves to `null` under Gradle and in tests, where it silently falls back to a hardcoded literal the generator does not keep current. We keep the explicit literals because they are correct in every context and because `.github/release-please-config.json` already lists this file in `extra-files`, so release-please bumps it alongside `pom.xml`, `build.gradle`, and `.fern/metadata.json`. Fern also emits `User-Agent` with a `com.deepgram.` prefix while leaving `X-Fern-SDK-Name` on the `com.deepgram:` Maven-coordinate form; we keep both on the colon form. Fern 4.22.1 tracks and closes child WebSockets before shutting down SDK-owned OkHttp resources; the permanent custom client wrappers inherit it, so do not add wrapper-local lifecycle code and retain Fern's lifecycle implementation during reconciliation. - `src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java` - carries bug fixes for `maxRetries(0)` semantics ("connect once, don't retry") and a configurable `connectionTimeoutMs` field (was hardcoded 4000ms), plus an `applyOptionsOverride(...)` hook used by `TransportWebSocketFactory` to apply per-transport reconnect policy; pull this back out once the fixes are upstreamed into the Fern generator. Use the standard `.bak` swap/restore workflow during regen review. - `src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java` and `src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java` - forward-compat patch (both clients). Fern's generated `handleIncomingMessage` dispatcher routes any unrecognized message type to `onError` with "Update your SDK version...", which makes a benign new server control frame look fatal to a deployed client. Patched so the unrecognized-type branch is a no-op — the raw frame is already delivered via `onMessage(String)` earlier in the method, so consumers still see it. Mirrors the JS/Python SDKs' forward-compat behavior and is regression-guarded by `src/test/java/com/deepgram/SpeakV2ForwardCompatTest.java` and `src/test/java/com/deepgram/ListenV2ForwardCompatTest.java`. These two clients also carry the streaming query-param patches described in the next entry. Use the standard `.bak` swap/restore workflow during regen review; re-apply the no-op to both after regen, and unfreeze once the generator stops treating unknown frames as errors. - `src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java` and `src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java` (and the v2 clients above) - streaming query-param patches on the generated `connect()` builders. Two fixes: (1) multi-value serialization — array-valued params (listen: `keyterm`, `keywords`, `replace`, `search`, `tag`, `extra`, `language_hint`; speak: `tag`) were serialized with `String.valueOf(union.get())`, collapsing a `List` into one param (`keyterm=[a, b]`) instead of repeats (`keyterm=a&keyterm=b`); (2) an `additionalProperties` escape hatch — the builder exposes `additionalProperty(key, value)` for unmodeled params (e.g. `no_delay`) but `connect()` never emitted them to the URL. Both patched to route through `QueryStringMapper(arraysAsRepeats=true)`, matching the REST path. Use the standard `.bak` swap/restore workflow during regen review; re-apply after regen and unfreeze once the generator emits array params as repeats and serializes `additionalProperties` on the WS `connect()` path (tracked as an upstream Fern request). -- All five generated WebSocket clients, including `src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java`, carry a `disconnect()` null guard so `close()` is safe before `connect()`. This keeps `AutoCloseable` safe for try-with-resources; re-apply the guards and retain the `.fernignore` entry until Fern emits them. - Fields-less message types carrying a manual `hashCode()` patch (Fern generates `equals()` but no `hashCode()` for these, violating the Object contract): `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java`, `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java`, and the `AgentV1*` event types `src/main/java/com/deepgram/resources/agent/v1/types/{AgentV1ListenUpdated,AgentV1SpeakUpdated,AgentV1AgentAudioDone,AgentV1SettingsApplied,AgentV1UserStartedSpeaking,AgentV1KeepAlive,AgentV1ThinkUpdated,AgentV1PromptUpdated,AgentV1ForceEndTurn}.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze all of them once the generator emits a matching equals/hashCode pair for fields-less types (tracked as an upstream Fern request). - `src/main/java/com/deepgram/types/DeepgramModel.java` - restores the `FLUX_RENEE_EN` constant that generator 4.18.0 dropped. The voice is live: `POST /v2/speak?model=flux-renee-en` returns 200 with valid audio, and the name resolves in the server's model registry (an invented `flux-*` name is rejected with `INVALID_QUERY_PARAMETER`), so the removal is a spec regression rather than a retirement, and dropping the constant would break 0.8.0 callers for nothing. Five touchpoints: the constant, the `Value` enum entry, the `visit()` case, the `valueOf()` case, and the `Visitor` method. **This file is unlike the other temporarily frozen ones — it receives frequent additive spec changes (4.18.0 alone added 25 constants), so on the next regen do NOT restore the `.bak` wholesale.** Diff the `.bak` against the newly generated file, carry forward every new voice, and re-apply only the `FLUX_RENEE_EN` touchpoints. Drop the patch and unfreeze once the spec lists the voice again (tracked as an upstream spec request). - Union default-variant fix on the agent listen-provider unions: `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java`. `version` is an optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits), but Fern points `@JsonTypeInfo` `defaultImpl` at the empty-bodied `_UnknownValue`, so such a payload deserializes to an unknown variant carrying `null` — `getProvider()` returns `null` and re-serialization emits `{"provider":null}`, silently dropping the provider on the wire. Patched to `defaultImpl = V2Value` on each; guarded by `src/test/java/com/deepgram/AgentSettingsProviderDefaultTest.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze once the generator stops defaulting unions to the empty `_UnknownValue` (tracked as an upstream Fern request). diff --git a/README.md b/README.md index b116cf4..94cab35 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ You can learn more about the Deepgram API at [developers.deepgram.com](https://d ### Migrating Between Versions -- [v0.9 to v0.10](./docs/Migrating-v0.9-to-v0.10.md) (current) +- [v0.10 to v0.11](./docs/Migrating-v0.10-to-v0.11.md) (current) +- [v0.9 to v0.10](./docs/Migrating-v0.9-to-v0.10.md) - [v0.8 to v0.9](./docs/Migrating-v0.8-to-v0.9.md) - [v0.7 to v0.8](./docs/Migrating-v0.7-to-v0.8.md) - [v0.6 to v0.7](./docs/Migrating-v0.6-to-v0.7.md) @@ -70,9 +71,9 @@ Get your API key from the [Deepgram Console](https://console.deepgram.com/). ### Resource Lifecycle -Close an SDK-created client when your application is finished with it. This releases the OkHttp dispatcher and -connection pool. Close each WebSocket client before closing its root client; root-client cleanup does not close active -WebSocket clients for you. +Close an SDK-created client when your application is finished with it. This disconnects any WebSocket clients still +connected through it, then releases the OkHttp dispatcher and connection pool. Close an individual WebSocket client +earlier when its work is complete; after the root client closes, new WebSocket connections fail. ```java import com.deepgram.DeepgramClient; From b9c3262c42b81ca17f3d7737f2bf71669e1b8dac Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 15:51:36 +0100 Subject: [PATCH 08/14] test: strengthen regeneration coverage --- docs/Migrating-v0.10-to-v0.11.md | 6 +++ .../java/com/deepgram/IntegrationTest.java | 9 ++-- .../java/com/deepgram/RegenTypesTest.java | 45 +++++++++++++++++++ ...StreamingAdditionalPropertiesWireTest.java | 35 ++++++++++----- 4 files changed, 79 insertions(+), 16 deletions(-) diff --git a/docs/Migrating-v0.10-to-v0.11.md b/docs/Migrating-v0.10-to-v0.11.md index 4d7cce0..b13f4b1 100644 --- a/docs/Migrating-v0.10-to-v0.11.md +++ b/docs/Migrating-v0.10-to-v0.11.md @@ -32,8 +32,14 @@ dependencies { | --- | --- | | `SharedTopics.getResults().get().getTopics().get().getSegments()` | `SharedTopics.getSegments()` | | `SharedIntents.getResults().get().getIntents().get().getSegments()` | `SharedIntents.getSegments()` | +| `SharedTopicsResults` | Removed; access segments through `SharedTopics.getSegments()` | +| `SharedTopicsResultsTopics` | Removed; access segments through `SharedTopics.getSegments()` | | `com.deepgram.types.SharedTopicsResultsTopicsSegmentsItem` | `com.deepgram.types.SharedTopicsSegmentsItem` | +| `com.deepgram.types.SharedTopicsResultsTopicsSegmentsItemTopicsItem` | `com.deepgram.types.SharedTopicsSegmentsItemTopicsItem` | +| `SharedIntentsResults` | Removed; access segments through `SharedIntents.getSegments()` | +| `SharedIntentsResultsIntents` | Removed; access segments through `SharedIntents.getSegments()` | | `com.deepgram.types.SharedIntentsResultsIntentsSegmentsItem` | `com.deepgram.types.SharedIntentsSegmentsItem` | +| `com.deepgram.types.SharedIntentsResultsIntentsSegmentsItemIntentsItem` | `com.deepgram.types.SharedIntentsSegmentsItemIntentsItem` | For example, replace the old Topics traversal: diff --git a/src/test/java/com/deepgram/IntegrationTest.java b/src/test/java/com/deepgram/IntegrationTest.java index 3148d82..251a37c 100644 --- a/src/test/java/com/deepgram/IntegrationTest.java +++ b/src/test/java/com/deepgram/IntegrationTest.java @@ -448,10 +448,11 @@ void testIntegration_SpeakV2ControlsWebSocket() throws Exception { assertThat(controlsApplied) .as("received controls_applied metadata") .isNotNull(); - // Pronunciation is Early Access; verify a valid report without requiring a non-zero count. - assertThat(controlsApplied.getPronunciationsApplied()).isGreaterThanOrEqualTo(0); - assertThat(controlsApplied.getBreaksApplied()).isGreaterThanOrEqualTo(0); - assertThat(controlsApplied.getPronunciationWarnings()).isGreaterThanOrEqualTo(0); + assertThat(controlsApplied.getPronunciationsApplied()) + .as("the submitted pronunciation was applied") + .isEqualTo(1); + assertThat(controlsApplied.getBreaksApplied()).isZero(); + assertThat(controlsApplied.getPronunciationWarnings()).isZero(); wsClient.sendClose(SpeakV2Close.builder().build()); } finally { diff --git a/src/test/java/com/deepgram/RegenTypesTest.java b/src/test/java/com/deepgram/RegenTypesTest.java index fffaed7..f2202c1 100644 --- a/src/test/java/com/deepgram/RegenTypesTest.java +++ b/src/test/java/com/deepgram/RegenTypesTest.java @@ -30,6 +30,8 @@ import com.deepgram.types.ListenV1ResponseMetadata; import com.deepgram.types.ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem; import com.deepgram.types.ListenV2Redact; +import com.deepgram.types.SharedIntents; +import com.deepgram.types.SharedTopics; import com.deepgram.types.ThinkSettingsV1FunctionsItem; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; @@ -376,4 +378,47 @@ void wordsItemSpeakerConfidenceAbsent() throws Exception { assertThat(word.getSpeaker()).isEmpty(); } } + + /** + * Coverage for the 2026-09-30 correction that removes the nonexistent nested {@code results} + * wrappers from Topics and Intents response objects. + */ + @Nested + @DisplayName("2026-09-30 Topics and Intents response shapes") + class TopicsAndIntentsShapes { + + @Test + @DisplayName("SharedTopics deserializes direct segments from the server payload") + void topicsDeserializeDirectSegments() throws Exception { + SharedTopics topics = MAPPER.readValue( + "{\"segments\":[{\"text\":\"Spacewalk\",\"topics\":[{\"topic\":\"Spacewalk\",\"confidence_score\":0.9}]}]}", + SharedTopics.class); + + assertThat(topics.getSegments()).hasValueSatisfying(segments -> { + assertThat(segments).singleElement().satisfies(segment -> { + assertThat(segment.getText()).contains("Spacewalk"); + assertThat(segment.getTopics()).hasValueSatisfying(values -> assertThat(values) + .singleElement() + .satisfies(value -> assertThat(value.getTopic()).contains("Spacewalk"))); + }); + }); + } + + @Test + @DisplayName("SharedIntents deserializes direct segments from the server payload") + void intentsDeserializeDirectSegments() throws Exception { + SharedIntents intents = MAPPER.readValue( + "{\"segments\":[{\"text\":\"Book a flight\",\"intents\":[{\"intent\":\"Book travel\",\"confidence_score\":0.9}]}]}", + SharedIntents.class); + + assertThat(intents.getSegments()).hasValueSatisfying(segments -> { + assertThat(segments).singleElement().satisfies(segment -> { + assertThat(segment.getText()).contains("Book a flight"); + assertThat(segment.getIntents()).hasValueSatisfying(values -> assertThat(values) + .singleElement() + .satisfies(value -> assertThat(value.getIntent()).contains("Book travel"))); + }); + }); + } + } } diff --git a/src/test/java/com/deepgram/StreamingAdditionalPropertiesWireTest.java b/src/test/java/com/deepgram/StreamingAdditionalPropertiesWireTest.java index 7ce1779..a67a4ae 100644 --- a/src/test/java/com/deepgram/StreamingAdditionalPropertiesWireTest.java +++ b/src/test/java/com/deepgram/StreamingAdditionalPropertiesWireTest.java @@ -6,6 +6,7 @@ import com.deepgram.types.ListenV1Model; import com.deepgram.types.ListenV2Model; import com.deepgram.types.SpeakV1Model; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import okhttp3.HttpUrl; @@ -71,6 +72,8 @@ private void assertEscapeHatchEmitted(HttpUrl url) { // The unmodeled params reached the wire, with a non-string value serialized correctly... assertThat(url.queryParameter("no_delay")).isEqualTo("true"); assertThat(url.queryParameter("custom_key")).isEqualTo("custom_value"); + assertThat(url.queryParameter("numeric_value")).isEqualTo("42"); + assertThat(url.queryParameterValues("multi_value")).containsExactly("first", "second"); // ...alongside (not instead of) the typed option. assertThat(url.queryParameterNames()).contains("model"); } @@ -83,9 +86,11 @@ void listenV1() throws Exception { .v1() .v1WebSocket() .connect(com.deepgram.resources.listen.v1.websocket.V1ConnectOptions.builder() - .model(ListenV1Model.NOVA3) - .additionalProperty("no_delay", true) - .additionalProperty("custom_key", "custom_value") + .model(ListenV1Model.NOVA3) + .additionalProperty("no_delay", true) + .additionalProperty("custom_key", "custom_value") + .additionalProperty("numeric_value", 42) + .additionalProperty("multi_value", List.of("first", "second")) .build()), "/v1/listen"); assertEscapeHatchEmitted(url); @@ -99,9 +104,11 @@ void listenV2() throws Exception { .v2() .v2WebSocket() .connect(com.deepgram.resources.listen.v2.websocket.V2ConnectOptions.builder() - .model(ListenV2Model.FLUX_GENERAL_EN) - .additionalProperty("no_delay", true) - .additionalProperty("custom_key", "custom_value") + .model(ListenV2Model.FLUX_GENERAL_EN) + .additionalProperty("no_delay", true) + .additionalProperty("custom_key", "custom_value") + .additionalProperty("numeric_value", 42) + .additionalProperty("multi_value", List.of("first", "second")) .build()), "/v2/listen"); assertEscapeHatchEmitted(url); @@ -115,9 +122,11 @@ void speakV1() throws Exception { .v1() .v1WebSocket() .connect(com.deepgram.resources.speak.v1.websocket.V1ConnectOptions.builder() - .model(SpeakV1Model.AURA2HERA_EN) - .additionalProperty("no_delay", true) - .additionalProperty("custom_key", "custom_value") + .model(SpeakV1Model.AURA2HERA_EN) + .additionalProperty("no_delay", true) + .additionalProperty("custom_key", "custom_value") + .additionalProperty("numeric_value", 42) + .additionalProperty("multi_value", List.of("first", "second")) .build()), "/v1/speak"); assertEscapeHatchEmitted(url); @@ -131,9 +140,11 @@ void speakV2() throws Exception { .v2() .v2WebSocket() .connect(com.deepgram.resources.speak.v2.websocket.V2ConnectOptions.builder() - .model("aura-2-thalia-en") - .additionalProperty("no_delay", true) - .additionalProperty("custom_key", "custom_value") + .model("aura-2-thalia-en") + .additionalProperty("no_delay", true) + .additionalProperty("custom_key", "custom_value") + .additionalProperty("numeric_value", 42) + .additionalProperty("multi_value", List.of("first", "second")) .build()), "/v2/speak"); assertEscapeHatchEmitted(url); From d85464398e05cb164f9addf661a2f1a523ffc9fd Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 16:16:00 +0100 Subject: [PATCH 09/14] test: cover lifecycle and retry changes --- docs/Migrating-v0.10-to-v0.11.md | 2 +- .../java/com/deepgram/ClientBuilderTest.java | 79 +++++++++++++++++++ .../deepgram/core/RetryInterceptorTest.java | 68 ++++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/docs/Migrating-v0.10-to-v0.11.md b/docs/Migrating-v0.10-to-v0.11.md index b13f4b1..48a5234 100644 --- a/docs/Migrating-v0.10-to-v0.11.md +++ b/docs/Migrating-v0.10-to-v0.11.md @@ -90,4 +90,4 @@ String pause = "\\{pause:500ms\\}"; String pronunciation = "\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}"; ``` -Pronunciation controls are Early Access. A pronunciation control cannot be combined with a non-default speed or with a pause; the API rejects those combinations with `CONTROL_COMBINATION_INVALID`. See [Flux TTS Controls](https://developers.deepgram.com/docs/tts-voice-controls) for supported syntax and availability. +Pronunciation controls are Early Access. A pronunciation control cannot be combined with a non-default speed or with a pause; the API rejects those combinations with `CONTROL_COMBINATION_INVALID`. The Flux TTS Controls guide publishes with the Controls release; consult that guide for supported syntax and availability once it is live. diff --git a/src/test/java/com/deepgram/ClientBuilderTest.java b/src/test/java/com/deepgram/ClientBuilderTest.java index 01d0f68..348164c 100644 --- a/src/test/java/com/deepgram/ClientBuilderTest.java +++ b/src/test/java/com/deepgram/ClientBuilderTest.java @@ -3,8 +3,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.deepgram.core.ClientOptions; import com.deepgram.core.Environment; +import com.deepgram.core.WebSocketFactory; +import com.deepgram.resources.listen.v1.websocket.V1ConnectOptions; +import com.deepgram.resources.listen.v1.websocket.V1WebSocketClient; +import com.deepgram.types.ListenV1Model; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.WebSocket; +import okio.ByteString; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -193,6 +204,74 @@ void closesWebSocketClientsBeforeConnecting() { } } + @Test + @DisplayName("closing the root client closes a connected WebSocket and blocks reconnects") + void closesConnectedWebSocketAndBlocksReconnects() throws Exception { + CountDownLatch closed = new CountDownLatch(1); + AtomicInteger connectionAttempts = new AtomicInteger(); + WebSocket connectedSocket = new WebSocket() { + @Override + public Request request() { + return new Request.Builder().url("http://localhost/").build(); + } + + @Override + public long queueSize() { + return 0; + } + + @Override + public boolean send(String text) { + return true; + } + + @Override + public boolean send(ByteString bytes) { + return true; + } + + @Override + public boolean close(int code, String reason) { + closed.countDown(); + return true; + } + + @Override + public void cancel() {} + }; + WebSocketFactory factory = (request, listener) -> { + connectionAttempts.incrementAndGet(); + listener.onOpen(connectedSocket, null); + return connectedSocket; + }; + DeepgramClient client = new DeepgramClient(ClientOptions.builder() + .environment(Environment.PRODUCTION) + .webSocketFactory(factory) + .build()); + V1WebSocketClient webSocket = client.listen().v1().v1WebSocket(); + V1ConnectOptions options = + V1ConnectOptions.builder().model(ListenV1Model.NOVA3).build(); + + try { + webSocket.connect(options).get(5, TimeUnit.SECONDS); + assertThat(connectionAttempts).hasValue(1); + + client.close(); + + assertThat(closed.await(5, TimeUnit.SECONDS)) + .as("root close disconnects the active WebSocket") + .isTrue(); + assertThatThrownBy(() -> webSocket.connect(options)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("root client has been closed"); + assertThat(connectionAttempts) + .as("closed clients do not open another WebSocket") + .hasValue(1); + } finally { + client.close(); + } + } + @Test @DisplayName("closing a client does not release caller-owned HTTP resources") void doesNotCloseCustomClientResources() { diff --git a/src/test/java/com/deepgram/core/RetryInterceptorTest.java b/src/test/java/com/deepgram/core/RetryInterceptorTest.java index 7017321..bfa177b 100644 --- a/src/test/java/com/deepgram/core/RetryInterceptorTest.java +++ b/src/test/java/com/deepgram/core/RetryInterceptorTest.java @@ -3,9 +3,16 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import okhttp3.MediaType; import okhttp3.OkHttpClient; +import okhttp3.Protocol; import okhttp3.Request; import okhttp3.Response; +import okhttp3.ResponseBody; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; @@ -37,6 +44,13 @@ private OkHttpClient buildClientWithRetries(int maxRetries) { .build(); } + private OkHttpClient buildClientWithRetriesAndCallTimeout(int maxRetries, long callTimeoutMillis) { + return new OkHttpClient.Builder() + .callTimeout(callTimeoutMillis, TimeUnit.MILLISECONDS) + .addInterceptor(new RetryInterceptor(maxRetries, Optional.of(0L), Optional.of(1000L), Optional.of(0.0))) + .build(); + } + private Request buildRequest() { return new Request.Builder().url(server.url("/test")).build(); } @@ -284,4 +298,58 @@ void testMultipleDifferentErrors() throws Exception { assertThat(server.getRequestCount()).isEqualTo(4); } } + + @Nested + @DisplayName("Retry transport failures") + class RetryTransportFailures { + + @Test + @DisplayName("returns the buffered retryable response when a later retry disconnects") + void preservesEarlierResponseAfterRetryTransportFailure() throws Exception { + AtomicInteger attempts = new AtomicInteger(); + client = new OkHttpClient.Builder() + .addInterceptor(new RetryInterceptor(1, Optional.of(0L), Optional.of(1000L), Optional.of(0.0))) + .addInterceptor(chain -> { + if (attempts.incrementAndGet() == 1) { + return new Response.Builder() + .request(chain.request()) + .protocol(Protocol.HTTP_1_1) + .code(500) + .message("original server error") + .body(ResponseBody.create( + "original server error".getBytes(StandardCharsets.UTF_8), + MediaType.get("text/plain"))) + .build(); + } + throw new IOException("retry transport failure"); + }) + .build(); + + Response response = client.newCall(buildRequest()).execute(); + + assertThat(response.code()).isEqualTo(500); + assertThat(response.body().string()).isEqualTo("original server error"); + assertThat(attempts).hasValue(2); + } + + @Test + @DisplayName("does not spend the next attempt timeout budget during Retry-After backoff") + void pausesCallTimeoutDuringRetryAfterBackoff() throws Exception { + client = buildClientWithRetriesAndCallTimeout(1, 100); + server.enqueue(new MockResponse() + .setResponseCode(429) + .setHeader("Retry-After", "1") + .setBody("rate limited")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("success")); + + long start = System.nanoTime(); + Response response = client.newCall(buildRequest()).execute(); + long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + + assertThat(response.code()).isEqualTo(200); + assertThat(response.body().string()).isEqualTo("success"); + assertThat(server.getRequestCount()).isEqualTo(2); + assertThat(elapsedMillis).isGreaterThanOrEqualTo(900L); + } + } } From abc7bfe3f479efcb1f6de00032c244eeede874f9 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 16:23:06 +0100 Subject: [PATCH 10/14] test: complete regeneration regression coverage --- docs/Migrating-v0.10-to-v0.11.md | 2 +- src/main/java/com/deepgram/core/ClientOptions.java | 9 +++++---- .../java/com/deepgram/core/RetryInterceptorTest.java | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/Migrating-v0.10-to-v0.11.md b/docs/Migrating-v0.10-to-v0.11.md index 48a5234..6c3b58c 100644 --- a/docs/Migrating-v0.10-to-v0.11.md +++ b/docs/Migrating-v0.10-to-v0.11.md @@ -83,7 +83,7 @@ The tradeoffs are intentional: requests can occupy a caller for longer, and buff ## Flux TTS Controls -Flux TTS now supports inline Controls for speed, pauses, and pronunciation overrides. Controls are additive: batch requests use `SpeakV2Request.text(...)`, while streaming requests use `SpeakV2Speak.text(...)`. +Flux TTS now supports inline Controls for speed, pauses, and pronunciation overrides. Controls are additive: batch requests use `SpeakV2Request.text(...)` for all three controls. Streaming requests use `SpeakV2Speak.text(...)` for pronunciation controls only; a pause marker on a streaming request fails with `DATA-0002`. ```java String pause = "\\{pause:500ms\\}"; diff --git a/src/main/java/com/deepgram/core/ClientOptions.java b/src/main/java/com/deepgram/core/ClientOptions.java index bfb382f..828cc2b 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -144,10 +144,11 @@ public Optional retryJitterFactor() { * OkHttpClient itself; an OkHttpClient supplied via httpClient is left running, since the * caller owns its lifecycle. *

- * In-flight calls are not cancelled or awaited, and any request issued after this method - * returns fails with a {@code RejectedExecutionException}. Options derived from this one via - * {@code Builder.from(...)} share the same dispatcher and connection pool, so closing either - * releases them for both. Calling this method more than once has no further effect. + * In-flight calls are not cancelled or awaited. When this client owns its OkHttpClient, later + * asynchronous HTTP requests can fail when the dispatcher rejects work, while synchronous + * requests may still run. An OkHttpClient supplied through {@code httpClient(...)} remains + * caller-owned and running. Options derived via {@code Builder.from(...)} share lifecycle + * state; calling this method more than once has no further effect. *

* WebSocket clients created from this client that are still connected are disconnected * first (whether or not the OkHttpClient is owned), so they stop reconnecting before the diff --git a/src/test/java/com/deepgram/core/RetryInterceptorTest.java b/src/test/java/com/deepgram/core/RetryInterceptorTest.java index bfa177b..25fc24e 100644 --- a/src/test/java/com/deepgram/core/RetryInterceptorTest.java +++ b/src/test/java/com/deepgram/core/RetryInterceptorTest.java @@ -316,6 +316,7 @@ void preservesEarlierResponseAfterRetryTransportFailure() throws Exception { .protocol(Protocol.HTTP_1_1) .code(500) .message("original server error") + .header("X-Retry-Source", "original") .body(ResponseBody.create( "original server error".getBytes(StandardCharsets.UTF_8), MediaType.get("text/plain"))) @@ -328,6 +329,7 @@ void preservesEarlierResponseAfterRetryTransportFailure() throws Exception { Response response = client.newCall(buildRequest()).execute(); assertThat(response.code()).isEqualTo(500); + assertThat(response.header("X-Retry-Source")).isEqualTo("original"); assertThat(response.body().string()).isEqualTo("original server error"); assertThat(attempts).hasValue(2); } From afece59df301cdf7f6e79b2b6e48aa7e94dd9c95 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 16:28:37 +0100 Subject: [PATCH 11/14] docs: clarify v0.11 migration behavior --- docs/Migrating-v0.10-to-v0.11.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Migrating-v0.10-to-v0.11.md b/docs/Migrating-v0.10-to-v0.11.md index 6c3b58c..578d11b 100644 --- a/docs/Migrating-v0.10-to-v0.11.md +++ b/docs/Migrating-v0.10-to-v0.11.md @@ -74,16 +74,16 @@ SpeakV2ConfigureFailureCode.Visitor visitor = new SpeakV2ConfigureFailur ## Retry behavior -Fern `4.21.2` and `4.22.2` change retry behavior without changing the public client-builder API. +Fern `4.21.2` and `4.22.2` change retry behavior for the SDK-created OkHttpClient without changing the public client-builder API. - The configured HTTP timeout is now applied to each attempt, rather than to the full retry loop. A request can therefore take longer than its configured timeout when retries and backoff occur. - Before retrying a retryable HTTP response, the SDK buffers that response. If a later retry fails with a transport error, the SDK returns the earlier HTTP response instead of discarding its status, headers, and body. -The tradeoffs are intentional: requests can occupy a caller for longer, and buffering holds a retryable response body in memory, but callers retain the actionable API error that triggered the retry instead of only observing a later connection failure. Set explicit timeouts and retry limits appropriate for latency-sensitive or large-response workloads, and continue to inspect returned HTTP errors during incident handling. +The tradeoffs are intentional: requests can occupy a caller for longer, and buffering holds a retryable response body in memory, but callers retain the actionable API error that triggered the retry instead of only observing a later connection failure. Set explicit timeouts and retry limits appropriate for latency-sensitive or large-response workloads, and continue to inspect returned HTTP errors during incident handling. Clients built with `.httpClient(...)` retain the caller-supplied client's timeouts, interceptors, and retry behavior. ## Flux TTS Controls -Flux TTS now supports inline Controls for speed, pauses, and pronunciation overrides. Controls are additive: batch requests use `SpeakV2Request.text(...)` for all three controls. Streaming requests use `SpeakV2Speak.text(...)` for pronunciation controls only; a pause marker on a streaming request fails with `DATA-0002`. +Flux TTS now supports inline Controls for speed, pauses, and pronunciation overrides. Controls are additive: batch requests use `SpeakV2Request.speed(...)` for speed and `SpeakV2Request.text(...)` for pause and pronunciation markers. Streaming requests use `V2ConnectOptions.speed(...)` or `SpeakV2Configure.speed(...)` for speed and `SpeakV2Speak.text(...)` for pronunciation controls; a pause marker on a streaming request fails with `DATA-0002`. ```java String pause = "\\{pause:500ms\\}"; From 6bc8726c1a858a5c0125c46885d288871aaf035f Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 16:34:04 +0100 Subject: [PATCH 12/14] docs: specify Controls transport behavior --- docs/Migrating-v0.10-to-v0.11.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Migrating-v0.10-to-v0.11.md b/docs/Migrating-v0.10-to-v0.11.md index 578d11b..7c9287e 100644 --- a/docs/Migrating-v0.10-to-v0.11.md +++ b/docs/Migrating-v0.10-to-v0.11.md @@ -79,7 +79,7 @@ Fern `4.21.2` and `4.22.2` change retry behavior for the SDK-created OkHttpClien - The configured HTTP timeout is now applied to each attempt, rather than to the full retry loop. A request can therefore take longer than its configured timeout when retries and backoff occur. - Before retrying a retryable HTTP response, the SDK buffers that response. If a later retry fails with a transport error, the SDK returns the earlier HTTP response instead of discarding its status, headers, and body. -The tradeoffs are intentional: requests can occupy a caller for longer, and buffering holds a retryable response body in memory, but callers retain the actionable API error that triggered the retry instead of only observing a later connection failure. Set explicit timeouts and retry limits appropriate for latency-sensitive or large-response workloads, and continue to inspect returned HTTP errors during incident handling. Clients built with `.httpClient(...)` retain the caller-supplied client's timeouts, interceptors, and retry behavior. +The tradeoffs are intentional: requests can occupy a caller for longer, and buffering holds a retryable response body in memory, but callers retain the actionable API error that triggered the retry instead of only observing a later connection failure. Set explicit timeouts and retry limits appropriate for latency-sensitive or large-response workloads, and continue to inspect returned HTTP errors during incident handling. Clients built with `.httpClient(...)` retain the caller-supplied retry policy, but the SDK still adds its logging and decompression interceptors. Setting `.timeout(...)` also overrides that client's call timeout. ## Flux TTS Controls @@ -90,4 +90,4 @@ String pause = "\\{pause:500ms\\}"; String pronunciation = "\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}"; ``` -Pronunciation controls are Early Access. A pronunciation control cannot be combined with a non-default speed or with a pause; the API rejects those combinations with `CONTROL_COMBINATION_INVALID`. The Flux TTS Controls guide publishes with the Controls release; consult that guide for supported syntax and availability once it is live. +Pronunciation controls are Early Access. On batch requests, pronunciation cannot be combined with a non-default speed or with a pause; the API returns HTTP 400 with `CONTROL_COMBINATION_INVALID`. On streaming requests, a pause marker or a pronunciation control with a non-default speed emits `DATA-0002` and closes the WebSocket. A mid-stream `Configure` that changes speed while a buffered turn contains a pronunciation control returns `ConfigureFailure` with `CONTROL_COMBINATION_INVALID`. The Flux TTS Controls guide publishes with the Controls release; consult that guide for supported syntax and availability once it is live. From bada0c2ad5a408d51f317f5c20843019b4baf0bb Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 16:39:17 +0100 Subject: [PATCH 13/14] docs: clarify historical Controls behavior --- docs/Migrating-v0.7-to-v0.8.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Migrating-v0.7-to-v0.8.md b/docs/Migrating-v0.7-to-v0.8.md index da7c8e2..7bb29db 100644 --- a/docs/Migrating-v0.7-to-v0.8.md +++ b/docs/Migrating-v0.7-to-v0.8.md @@ -164,7 +164,7 @@ Optional version = google.getVersion(); `SpeakV2SpeechMetadataControlsApplied` gained a required `breaksApplied` (`int`) field, reflecting a new `breaks_applied` field in the server payload. `SpeakV2SpeechMetadataControlsApplied` is a **server-emitted (read-only)** message, so most applications only read it — a new `getBreaksApplied()` getter is now available and no migration is needed for read paths. -> **Inline pause controls are not applied at launch** — support is coming soon. The server sends `breaks_applied` on every turn, but the value is always `0` until pause controls ship. The same is true of `pronunciationsApplied` and `pronunciationWarnings`. Read the field if you like, but do not branch on a non-zero value yet. +> **At the v0.8.0 launch**, inline pause controls were not applied and `breaks_applied` was always `0`. That historical limitation no longer applies to Flux TTS batch requests; see the [v0.10 to v0.11 migration guide](./Migrating-v0.10-to-v0.11.md#flux-tts-controls) for current Controls behavior and streaming restrictions. If you construct this type directly (uncommon — e.g. in tests), the staged builder now requires a `breaksApplied(int)` step between `pronunciationsApplied(...)` and `pronunciationWarnings(...)`. @@ -182,7 +182,7 @@ SpeakV2SpeechMetadataControlsApplied.builder() ```java SpeakV2SpeechMetadataControlsApplied.builder() .pronunciationsApplied(2) - .breaksApplied(0) // always 0 at launch — pause controls are not yet applied + .breaksApplied(0) // v0.8 launch behavior; Controls behavior has since changed .pronunciationWarnings(0) .build(); @@ -230,4 +230,4 @@ int breaks = controlsApplied.getBreaksApplied(); - [ ] Add a `breaksApplied(...)` step to any hand-built `SpeakV2SpeechMetadataControlsApplied` — pass `0`, since inline pause controls are not applied at launch (read paths need no change) - [ ] Rebuild your project and fix any remaining references to the changed signatures - [ ] (Optional) Adopt Speak V2 interrupt/configure, Listen V2 `redact`, client retry tuning, and the new Flux voices -``` \ No newline at end of file +``` From d836283480fd442d60052e3bbd62d702ebe61faa Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 30 Sep 2026 16:44:20 +0100 Subject: [PATCH 14/14] docs: remove stale Controls availability copy --- docs/Migrating-v0.7-to-v0.8.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Migrating-v0.7-to-v0.8.md b/docs/Migrating-v0.7-to-v0.8.md index 7bb29db..55ebdcd 100644 --- a/docs/Migrating-v0.7-to-v0.8.md +++ b/docs/Migrating-v0.7-to-v0.8.md @@ -8,7 +8,7 @@ The three breaking changes are: 1. **`AgentV1UpdateListenListen.getProvider()` retyped** from `DeepgramListenProviderV2` to the new `AgentV1UpdateListenListenProvider` V1/V2 union — the API now models the `provider` field as a versioned discriminated union. 2. **`Google.getVersion()` retyped** from `Optional` to `Optional` — the Google think-provider `version` field is now an enum. -3. **`SpeakV2SpeechMetadataControlsApplied` gained a required `breaksApplied` field** — the builder chain now includes a `breaksApplied(int)` step between `pronunciationsApplied(...)` and `pronunciationWarnings(...)`. The server always sends `0` at launch, because inline pause controls are not yet applied. +3. **`SpeakV2SpeechMetadataControlsApplied` gained a required `breaksApplied` field** — the builder chain now includes a `breaksApplied(int)` step between `pronunciationsApplied(...)` and `pronunciationWarnings(...)`. At the v0.8 launch, the server sent `0`; current Flux TTS batch and WebSocket behavior is documented in the [v0.10 to v0.11 migration guide](./Migrating-v0.10-to-v0.11.md#flux-tts-controls). ## Table of Contents @@ -206,7 +206,7 @@ int breaks = controlsApplied.getBreaksApplied(); 1. **Agent update-listen provider union**: `AgentV1UpdateListenListen.getProvider()` / `provider(...)` now use `AgentV1UpdateListenListenProvider` (V1/V2 union) instead of `DeepgramListenProviderV2`. 2. **Google think-provider version enum**: `Google.getVersion()` / `version(...)` now use `GoogleThinkProviderVersion` instead of `String`. -3. **Speak V2 controls-applied field**: `SpeakV2SpeechMetadataControlsApplied` adds a required `breaksApplied` field (new builder step; new `getBreaksApplied()` getter). Always `0` at launch — inline pause controls are not yet applied. +3. **Speak V2 controls-applied field**: `SpeakV2SpeechMetadataControlsApplied` adds a required `breaksApplied` field (new builder step; new `getBreaksApplied()` getter). At the v0.8 launch the value was `0`; see the [v0.10 to v0.11 migration guide](./Migrating-v0.10-to-v0.11.md#flux-tts-controls) for current Controls behavior. ### Changed Signatures @@ -227,7 +227,7 @@ int breaks = controlsApplied.getBreaksApplied(); - [ ] Upgrade to `com.deepgram:deepgram-java-sdk:0.8.0` - [ ] Wrap `AgentV1UpdateListenListen` providers in `AgentV1UpdateListenListenProvider.v2(...)` (or `.v1(...)`) and read them via `getV2()` / `getV1()` - [ ] Replace `Google` `version` string literals with `GoogleThinkProviderVersion` constants and update any `Optional getVersion()` reads -- [ ] Add a `breaksApplied(...)` step to any hand-built `SpeakV2SpeechMetadataControlsApplied` — pass `0`, since inline pause controls are not applied at launch (read paths need no change) +- [ ] Add a `breaksApplied(...)` step to any hand-built `SpeakV2SpeechMetadataControlsApplied` — use `0` when reproducing the v0.8 launch payload shape (read paths need no change) - [ ] Rebuild your project and fix any remaining references to the changed signatures - [ ] (Optional) Adopt Speak V2 interrupt/configure, Listen V2 `redact`, client retry tuning, and the new Flux voices ```