From 07d92fbc8466cc246824f2f4096dc7745fa5d4f7 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Mon, 15 Jun 2026 12:40:35 +0100 Subject: [PATCH 1/7] chore: initialize SDK regeneration branch From 77d4510be060b7c1ff1dbbbc1a9e1b9267e6cd6c Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Mon, 15 Jun 2026 12:41:38 +0100 Subject: [PATCH 2/7] chore: unfreeze files pending regen --- .fernignore | 4 +- .../com/deepgram/core/ClientOptions.java.bak | 241 ++++++++ .../ReconnectingWebSocketListener.java.bak | 550 ++++++++++++++++++ 3 files changed, 793 insertions(+), 2 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 diff --git a/.fernignore b/.fernignore index a14a77f..7b48e95 100644 --- a/.fernignore +++ b/.fernignore @@ -14,7 +14,7 @@ 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. -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/ @@ -22,7 +22,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 # 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..1cf3a62 --- /dev/null +++ b/src/main/java/com/deepgram/core/ClientOptions.java.bak @@ -0,0 +1,241 @@ +/** + * 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 int timeout; + + private final int maxRetries; + + private final Optional webSocketFactory; + + private final Optional logging; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout, + int maxRetries, + 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.5.0"); // 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.5.0"); // x-release-please-version + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + this.maxRetries = maxRetries; + 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 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 timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + 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) { + 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; + } + + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + 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)); + } + + Logger logger = Logger.from(this.logging); + httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions( + environment, + headers, + headerSuppliers, + httpClient, + this.timeout.get(), + this.maxRetries, + 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.headers.putAll(clientOptions.headers); + builder.headerSuppliers.putAll(clientOptions.headerSuppliers); + builder.maxRetries = clientOptions.maxRetries(); + 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..e10e5af --- /dev/null +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak @@ -0,0 +1,550 @@ +/** + * 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.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 { + // Option-derived fields are volatile (not final) so {@link #applyOptionsOverride} can rewire them + // after construction — used by {@code TransportWebSocketFactory} to honour + // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. + private volatile long minReconnectionDelayMs; + + private volatile long maxReconnectionDelayMs; + + private volatile double reconnectionDelayGrowFactor; + + private volatile int maxRetries; + + private final int maxEnqueuedMessages; + + private volatile long connectionTimeoutMs; + + 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.minReconnectionDelayMs = options.minReconnectionDelayMs; + this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; + this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; + this.maxRetries = options.maxRetries; + this.maxEnqueuedMessages = options.maxEnqueuedMessages; + this.connectionTimeoutMs = options.connectionTimeoutMs; + this.connectionSupplier = connectionSupplier; + } + + /** + * Replaces the option-derived parameters on this listener at runtime. Used by + * {@code TransportWebSocketFactory} to apply {@code DeepgramTransportFactory.reconnectOptions()} + * without requiring edits to the generated per-resource WebSocket clients. {@code maxEnqueuedMessages} + * is intentionally not overridden — the message queue is sized at construction. + * + *

Thread-safety: option-derived fields are volatile; reads observe the latest write. The + * initial connect() call may have already started before the override lands, so for the very + * first attempt the original options apply; the override takes effect from the next attempt + * onwards. For the SageMaker storm-suppression case ({@code maxRetries(0)}) this is fine + * because the initial attempt's gate ({@code retryCount > maxRetries} with {@code retryCount=0}) + * always passes regardless. + * + * @param options replacement options; {@code null} is a no-op. + */ + public void applyOptionsOverride(ReconnectOptions options) { + if (options == null) { + return; + } + this.minReconnectionDelayMs = options.minReconnectionDelayMs; + this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; + this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; + this.maxRetries = options.maxRetries; + this.connectionTimeoutMs = options.connectionTimeoutMs; + } + + /** + * 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 — the initial attempt always proceeds. + * {@code maxRetries(0)} means "connect once, don't retry" (not "refuse to connect"). + * + * 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; + } + // retryCount is incremented inside scheduleReconnect() before re-entering connect(), + // so on the initial call retryCount == 0 and we always proceed. The cap applies to + // retries only — maxRetries(0) blocks retries but allows the initial attempt. + if (retryCount.get() > maxRetries) { + connectLock.set(false); + return; + } + try { + CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); + try { + webSocket = connectionFuture.get(connectionTimeoutMs, MILLISECONDS); + } catch (TimeoutException e) { + connectionFuture.cancel(true); + TimeoutException timeoutError = + new TimeoutException("WebSocket connection timeout after " + 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) { + WebSocket ws = webSocket; + if (ws != null) { + boolean sent = ws.send(message); + 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) { + WebSocket ws = webSocket; + if (ws != null) { + boolean sent = ws.send(data); + 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(); + } + } + + /** + * @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 (code != 1000 && shouldReconnect.get()) { + scheduleReconnect(); + } + } + + /** + * 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() { + if (retryCount.get() == 1) { + return minReconnectionDelayMs; + } + 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. + */ + 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}. + * Each call to {@link ReconnectingWebSocketListener#connect()} will wait at most + * this long for the underlying WebSocket factory to produce a connected socket. + */ + 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 + * - connectionTimeoutMs is positive + * + * @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); + } + } + } +} From fc42577a45fe42347f847738a02161cd23fc0e50 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:50:09 +0000 Subject: [PATCH 3/7] SDK regeneration --- .fern/metadata.json | 8 +- .../java/com/deepgram/core/ClientOptions.java | 6 +- .../deepgram/core/DateTimeDeserializer.java | 12 ++- .../core/ReconnectingWebSocketListener.java | 74 ++----------- .../agent/v1/websocket/V1WebSocketClient.java | 3 +- .../v1/media/requests/ListenV1RequestUrl.java | 16 +-- .../MediaTranscribeRequestOctetStream.java | 91 ++++++++-------- .../listen/v1/types/DiarizeModel.java | 82 ++++++++++++++ .../listen/v1/websocket/V1ConnectOptions.java | 59 +++++++++++ .../v1/websocket/V1WebSocketClient.java | 7 +- .../listen/v2/types/ListenV2CloseStream.java | 65 ++---------- .../v2/types/ListenV2CloseStreamType.java | 95 ----------------- .../v2/types/ListenV2TurnInfoWordsItem.java | 100 +++++++++++++++++- .../listen/v2/websocket/V2ConnectOptions.java | 33 ++++++ .../v2/websocket/V2WebSocketClient.java | 8 +- .../v1/models/requests/ModelsListRequest.java | 4 +- .../requests/BreakdownListRequest.java | 16 +-- .../fields/requests/FieldsListRequest.java | 6 +- .../requests/PurchasesListRequest.java | 4 +- .../keys/requests/KeysListRequest.java | 4 +- .../models/requests/ModelsListRequest.java | 4 +- .../projects/requests/ProjectsGetRequest.java | 6 +- .../requests/RequestsListRequest.java | 22 ++-- .../requests/BreakdownGetRequest.java | 92 ++++++++-------- .../fields/requests/FieldsListRequest.java | 6 +- .../usage/requests/UsageGetRequest.java | 90 ++++++++-------- .../v1/text/requests/TextAnalyzeRequest.java | 25 +++-- .../speak/v1/websocket/V1WebSocketClient.java | 3 +- .../types/DeepgramListenProviderV2.java | 44 ++++---- .../DeepgramListenProviderV2LanguageHint.java | 99 ----------------- .../types/ListenV2ProfanityFilter.java | 84 +++++++++++++++ 31 files changed, 618 insertions(+), 550 deletions(-) create mode 100644 src/main/java/com/deepgram/resources/listen/v1/types/DiarizeModel.java delete mode 100644 src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStreamType.java delete mode 100644 src/main/java/com/deepgram/types/DeepgramListenProviderV2LanguageHint.java create mode 100644 src/main/java/com/deepgram/types/ListenV2ProfanityFilter.java diff --git a/.fern/metadata.json b/.fern/metadata.json index 1892a3d..23e6a0e 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,7 +1,7 @@ { - "cliVersion": "4.107.0", + "cliVersion": "5.44.6", "generatorName": "fernapi/fern-java-sdk", - "generatorVersion": "4.6.2", + "generatorVersion": "4.10.1", "generatorConfig": { "package-prefix": "com.deepgram", "base-api-exception-class-name": "DeepgramHttpException", @@ -11,8 +11,8 @@ }, "enable-wire-tests": true }, - "originGitCommit": "d228f82e93aaa8aa77f978d458cf912f3daaa8c1", + "originGitCommit": "0be656a350cef88f6586d6cb4aa28f8fc25b30ea", "originGitCommitIsDirty": true, "invokedBy": "manual", - "sdkVersion": "0.5.0" + "sdkVersion": "0.5.1" } \ No newline at end of file diff --git a/src/main/java/com/deepgram/core/ClientOptions.java b/src/main/java/com/deepgram/core/ClientOptions.java index 1cf3a62..1efe669 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -41,10 +41,10 @@ private ClientOptions( this.headers.putAll(headers); this.headers.putAll(new HashMap() { { - put("User-Agent", "com.deepgram:deepgram-java-sdk/0.5.0"); // x-release-please-version + put("User-Agent", "com.deepgram:deepgram-sdk/0.5.1"); put("X-Fern-Language", "JAVA"); - put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); - put("X-Fern-SDK-Version", "0.5.0"); // x-release-please-version + put("X-Fern-SDK-Name", "com.deepgram.fern:api-sdk"); + put("X-Fern-SDK-Version", "0.5.1"); } }); this.headerSuppliers = headerSuppliers; diff --git a/src/main/java/com/deepgram/core/DateTimeDeserializer.java b/src/main/java/com/deepgram/core/DateTimeDeserializer.java index 6eccd67..0a8e0b6 100644 --- a/src/main/java/com/deepgram/core/DateTimeDeserializer.java +++ b/src/main/java/com/deepgram/core/DateTimeDeserializer.java @@ -14,6 +14,7 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalQueries; @@ -42,8 +43,15 @@ public OffsetDateTime deserialize(JsonParser parser, DeserializationContext cont if (token == JsonToken.VALUE_NUMBER_INT) { return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); } else { - TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( - parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + String value = parser.getValueAsString(); + TemporalAccessor temporal; + try { + temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest(value, OffsetDateTime::from, LocalDateTime::from); + } catch (DateTimeParseException e) { + // Fall back to space-separated format (e.g. "2025-02-15 10:30:00+00:00"). + temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + value.replace(' ', 'T'), OffsetDateTime::from, LocalDateTime::from); + } if (temporal.query(TemporalQueries.offset()) == null) { return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java index e10e5af..0ca455a 100644 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java @@ -25,21 +25,16 @@ * Provides production-ready resilience for WebSocket connections. */ public abstract class ReconnectingWebSocketListener extends WebSocketListener { - // Option-derived fields are volatile (not final) so {@link #applyOptionsOverride} can rewire them - // after construction — used by {@code TransportWebSocketFactory} to honour - // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. - private volatile long minReconnectionDelayMs; + private final long minReconnectionDelayMs; - private volatile long maxReconnectionDelayMs; + private final long maxReconnectionDelayMs; - private volatile double reconnectionDelayGrowFactor; + private final double reconnectionDelayGrowFactor; - private volatile int maxRetries; + private final int maxRetries; private final int maxEnqueuedMessages; - private volatile long connectionTimeoutMs; - private final AtomicInteger retryCount = new AtomicInteger(0); private final AtomicBoolean connectLock = new AtomicBoolean(false); @@ -71,44 +66,16 @@ public ReconnectingWebSocketListener( this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; this.maxRetries = options.maxRetries; this.maxEnqueuedMessages = options.maxEnqueuedMessages; - this.connectionTimeoutMs = options.connectionTimeoutMs; this.connectionSupplier = connectionSupplier; } - /** - * Replaces the option-derived parameters on this listener at runtime. Used by - * {@code TransportWebSocketFactory} to apply {@code DeepgramTransportFactory.reconnectOptions()} - * without requiring edits to the generated per-resource WebSocket clients. {@code maxEnqueuedMessages} - * is intentionally not overridden — the message queue is sized at construction. - * - *

Thread-safety: option-derived fields are volatile; reads observe the latest write. The - * initial connect() call may have already started before the override lands, so for the very - * first attempt the original options apply; the override takes effect from the next attempt - * onwards. For the SageMaker storm-suppression case ({@code maxRetries(0)}) this is fine - * because the initial attempt's gate ({@code retryCount > maxRetries} with {@code retryCount=0}) - * always passes regardless. - * - * @param options replacement options; {@code null} is a no-op. - */ - public void applyOptionsOverride(ReconnectOptions options) { - if (options == null) { - return; - } - this.minReconnectionDelayMs = options.minReconnectionDelayMs; - this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; - this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; - this.maxRetries = options.maxRetries; - this.connectionTimeoutMs = options.connectionTimeoutMs; - } - /** * 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 — the initial attempt always proceeds. - * {@code maxRetries(0)} means "connect once, don't retry" (not "refuse to connect"). + * - Retry count not incremented for initial connection attempt * * Error handling: * - TimeoutException: Includes retry attempt context @@ -119,21 +86,18 @@ public void connect() { if (!connectLock.compareAndSet(false, true)) { return; } - // retryCount is incremented inside scheduleReconnect() before re-entering connect(), - // so on the initial call retryCount == 0 and we always proceed. The cap applies to - // retries only — maxRetries(0) blocks retries but allows the initial attempt. - if (retryCount.get() > maxRetries) { + if (retryCount.get() >= maxRetries) { connectLock.set(false); return; } try { CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); try { - webSocket = connectionFuture.get(connectionTimeoutMs, MILLISECONDS); + webSocket = connectionFuture.get(4000, MILLISECONDS); } catch (TimeoutException e) { connectionFuture.cancel(true); TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + connectionTimeoutMs + " milliseconds" + new TimeoutException("WebSocket connection timeout after " + 4000 + " milliseconds" + (retryCount.get() > 0 ? " (retry attempt #" + retryCount.get() : " (initial connection attempt)")); @@ -435,15 +399,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() { @@ -461,15 +422,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) { @@ -497,16 +455,6 @@ public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { return this; } - /** - * Sets the per-attempt connection timeout in milliseconds. Defaults to {@code 4000}. - * Each call to {@link ReconnectingWebSocketListener#connect()} will wait at most - * this long for the underlying WebSocket factory to produce a connected socket. - */ - public Builder connectionTimeoutMs(long connectionTimeoutMs) { - this.connectionTimeoutMs = connectionTimeoutMs; - return this; - } - /** * Builds the ReconnectOptions with validation. * @@ -515,7 +463,6 @@ public Builder connectionTimeoutMs(long connectionTimeoutMs) { * - minReconnectionDelayMs <= maxReconnectionDelayMs * - reconnectionDelayGrowFactor >= 1.0 * - maxRetries and maxEnqueuedMessages are non-negative - * - connectionTimeoutMs is positive * * @return The validated ReconnectOptions instance * @throws IllegalArgumentException if configuration is invalid @@ -540,9 +487,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/resources/agent/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java index 59afaf1..567290d 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 @@ -7,6 +7,7 @@ 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; @@ -141,7 +142,7 @@ public CompletableFuture connect() { } HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers(null).forEach(requestBuilder::addHeader); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); this.readyState = WebSocketReadyState.CONNECTING; ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null diff --git a/src/main/java/com/deepgram/resources/listen/v1/media/requests/ListenV1RequestUrl.java b/src/main/java/com/deepgram/resources/listen/v1/media/requests/ListenV1RequestUrl.java index d59ed17..d456f3a 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/media/requests/ListenV1RequestUrl.java +++ b/src/main/java/com/deepgram/resources/listen/v1/media/requests/ListenV1RequestUrl.java @@ -336,7 +336,7 @@ public Optional getDetectLanguage() { } /** - * @return Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0 + * @return Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0. */ @JsonIgnore public Optional getDiarize() { @@ -344,7 +344,7 @@ public Optional getDiarize() { } /** - * @return Select and enable a specific batch diarization model version. If specifying this parameter, you should not set the deprecated diarize=true parameter. Not accepted on streaming requests. + * @return Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set the deprecated diarize=true parameter. For batch, supported values are latest (currently v2), v1, and v2. For streaming, supported values are latest (currently v1) and v1; v2 returns a validation error on streaming requests. */ @JsonIgnore public Optional getDiarizeModel() { @@ -752,14 +752,14 @@ public interface _FinalStage { _FinalStage detectLanguage(Boolean detectLanguage); /** - *

Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0

+ *

Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0.

*/ _FinalStage diarize(Optional diarize); _FinalStage diarize(Boolean diarize); /** - *

Select and enable a specific batch diarization model version. If specifying this parameter, you should not set the deprecated diarize=true parameter. Not accepted on streaming requests.

+ *

Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set the deprecated diarize=true parameter. For batch, supported values are latest (currently v2), v1, and v2. For streaming, supported values are latest (currently v1) and v1; v2 returns a validation error on streaming requests.

*/ _FinalStage diarizeModel(Optional diarizeModel); @@ -1359,7 +1359,7 @@ public _FinalStage dictation(Optional dictation) { } /** - *

Select and enable a specific batch diarization model version. If specifying this parameter, you should not set the deprecated diarize=true parameter. Not accepted on streaming requests.

+ *

Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set the deprecated diarize=true parameter. For batch, supported values are latest (currently v2), v1, and v2. For streaming, supported values are latest (currently v1) and v1; v2 returns a validation error on streaming requests.

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

Select and enable a specific batch diarization model version. If specifying this parameter, you should not set the deprecated diarize=true parameter. Not accepted on streaming requests.

+ *

Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set the deprecated diarize=true parameter. For batch, supported values are latest (currently v2), v1, and v2. For streaming, supported values are latest (currently v1) and v1; v2 returns a validation error on streaming requests.

*/ @java.lang.Override @JsonSetter(value = "diarize_model", nulls = Nulls.SKIP) @@ -1379,7 +1379,7 @@ public _FinalStage diarizeModel(Optional dia } /** - *

Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0

+ *

Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0.

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

Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0

+ *

Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0.

*/ @java.lang.Override @JsonSetter(value = "diarize", nulls = Nulls.SKIP) diff --git a/src/main/java/com/deepgram/resources/listen/v1/media/requests/MediaTranscribeRequestOctetStream.java b/src/main/java/com/deepgram/resources/listen/v1/media/requests/MediaTranscribeRequestOctetStream.java index 078a697..bbf624f 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/media/requests/MediaTranscribeRequestOctetStream.java +++ b/src/main/java/com/deepgram/resources/listen/v1/media/requests/MediaTranscribeRequestOctetStream.java @@ -14,7 +14,6 @@ import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestVersion; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -194,7 +193,7 @@ private MediaTranscribeRequestOctetStream( /** * @return Arbitrary key-value pairs that are attached to the API response for usage in downstream processing */ - @JsonIgnore + @JsonProperty("extra") public Optional> getExtra() { return extra; } @@ -202,7 +201,7 @@ public Optional> getExtra() { /** * @return Label your requests for the purpose of identification during usage reporting */ - @JsonIgnore + @JsonProperty("tag") public Optional> getTag() { return tag; } @@ -210,7 +209,7 @@ public Optional> getTag() { /** * @return Custom topics you want the model to detect within your input audio or text if present Submit up to 100. */ - @JsonIgnore + @JsonProperty("custom_topic") public Optional> getCustomTopic() { return customTopic; } @@ -218,7 +217,7 @@ public Optional> getCustomTopic() { /** * @return Custom intents you want the model to detect within your input audio if present */ - @JsonIgnore + @JsonProperty("custom_intent") public Optional> getCustomIntent() { return customIntent; } @@ -226,7 +225,7 @@ public Optional> getCustomIntent() { /** * @return Key term prompting can boost or suppress specialized terminology and brands. Only compatible with Nova-3 */ - @JsonIgnore + @JsonProperty("keyterm") public Optional> getKeyterm() { return keyterm; } @@ -234,7 +233,7 @@ public Optional> getKeyterm() { /** * @return Keywords can boost or suppress specialized terminology and brands */ - @JsonIgnore + @JsonProperty("keywords") public Optional> getKeywords() { return keywords; } @@ -242,7 +241,7 @@ public Optional> getKeywords() { /** * @return Search for terms or phrases in submitted audio and replaces them */ - @JsonIgnore + @JsonProperty("replace") public Optional> getReplace() { return replace; } @@ -250,7 +249,7 @@ public Optional> getReplace() { /** * @return Search for terms or phrases in submitted audio */ - @JsonIgnore + @JsonProperty("search") public Optional> getSearch() { return search; } @@ -258,7 +257,7 @@ public Optional> getSearch() { /** * @return URL to which we'll make the callback request */ - @JsonIgnore + @JsonProperty("callback") public Optional getCallback() { return callback; } @@ -266,7 +265,7 @@ public Optional getCallback() { /** * @return HTTP method by which the callback request will be made */ - @JsonIgnore + @JsonProperty("callback_method") public Optional getCallbackMethod() { return callbackMethod; } @@ -274,7 +273,7 @@ public Optional getCallbackMethod() { /** * @return Recognizes the sentiment throughout a transcript or text */ - @JsonIgnore + @JsonProperty("sentiment") public Optional getSentiment() { return sentiment; } @@ -282,7 +281,7 @@ public Optional getSentiment() { /** * @return Summarize content. For Listen API, supports string version option. For Read API, accepts boolean only. */ - @JsonIgnore + @JsonProperty("summarize") public Optional getSummarize() { return summarize; } @@ -290,7 +289,7 @@ public Optional getSummarize() { /** * @return Detect topics throughout a transcript or text */ - @JsonIgnore + @JsonProperty("topics") public Optional getTopics() { return topics; } @@ -298,7 +297,7 @@ public Optional getTopics() { /** * @return Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param */ - @JsonIgnore + @JsonProperty("custom_topic_mode") public Optional getCustomTopicMode() { return customTopicMode; } @@ -306,7 +305,7 @@ public Optional getCustomTopicMode() { /** * @return Recognizes speaker intent throughout a transcript or text */ - @JsonIgnore + @JsonProperty("intents") public Optional getIntents() { return intents; } @@ -314,7 +313,7 @@ public Optional getIntents() { /** * @return Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in the custom_intent param. */ - @JsonIgnore + @JsonProperty("custom_intent_mode") public Optional getCustomIntentMode() { return customIntentMode; } @@ -322,7 +321,7 @@ public Optional getCustomIntentMode() { /** * @return Identifies and extracts key entities from content in submitted audio */ - @JsonIgnore + @JsonProperty("detect_entities") public Optional getDetectEntities() { return detectEntities; } @@ -330,23 +329,23 @@ public Optional getDetectEntities() { /** * @return Identifies the dominant language spoken in submitted audio */ - @JsonIgnore + @JsonProperty("detect_language") public Optional getDetectLanguage() { return detectLanguage; } /** - * @return Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0 + * @return Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0. */ - @JsonIgnore + @JsonProperty("diarize") public Optional getDiarize() { return diarize; } /** - * @return Select and enable a specific batch diarization model version. If specifying this parameter, you should not set the deprecated diarize=true parameter. Not accepted on streaming requests. + * @return Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set the deprecated diarize=true parameter. For batch, supported values are latest (currently v2), v1, and v2. For streaming, supported values are latest (currently v1) and v1; v2 returns a validation error on streaming requests. */ - @JsonIgnore + @JsonProperty("diarize_model") public Optional getDiarizeModel() { return diarizeModel; } @@ -354,7 +353,7 @@ public Optional getDiarizeModel() { /** * @return Dictation mode for controlling formatting with dictated speech */ - @JsonIgnore + @JsonProperty("dictation") public Optional getDictation() { return dictation; } @@ -362,7 +361,7 @@ public Optional getDictation() { /** * @return Specify the expected encoding of your submitted audio */ - @JsonIgnore + @JsonProperty("encoding") public Optional getEncoding() { return encoding; } @@ -370,7 +369,7 @@ public Optional getEncoding() { /** * @return Filler Words can help transcribe interruptions in your audio, like "uh" and "um" */ - @JsonIgnore + @JsonProperty("filler_words") public Optional getFillerWords() { return fillerWords; } @@ -378,7 +377,7 @@ public Optional getFillerWords() { /** * @return The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available */ - @JsonIgnore + @JsonProperty("language") public Optional getLanguage() { return language; } @@ -386,7 +385,7 @@ public Optional getLanguage() { /** * @return Spoken measurements will be converted to their corresponding abbreviations */ - @JsonIgnore + @JsonProperty("measurements") public Optional getMeasurements() { return measurements; } @@ -394,7 +393,7 @@ public Optional getMeasurements() { /** * @return AI model used to process submitted audio */ - @JsonIgnore + @JsonProperty("model") public Optional getModel() { return model; } @@ -402,7 +401,7 @@ public Optional getModel() { /** * @return Transcribe each audio channel independently */ - @JsonIgnore + @JsonProperty("multichannel") public Optional getMultichannel() { return multichannel; } @@ -410,7 +409,7 @@ public Optional getMultichannel() { /** * @return Numerals converts numbers from written format to numerical format */ - @JsonIgnore + @JsonProperty("numerals") public Optional getNumerals() { return numerals; } @@ -418,7 +417,7 @@ public Optional getNumerals() { /** * @return Splits audio into paragraphs to improve transcript readability */ - @JsonIgnore + @JsonProperty("paragraphs") public Optional getParagraphs() { return paragraphs; } @@ -426,7 +425,7 @@ public Optional getParagraphs() { /** * @return Profanity Filter looks for recognized profanity and converts it to the nearest recognized non-profane word or removes it from the transcript completely */ - @JsonIgnore + @JsonProperty("profanity_filter") public Optional getProfanityFilter() { return profanityFilter; } @@ -434,7 +433,7 @@ public Optional getProfanityFilter() { /** * @return Add punctuation and capitalization to the transcript */ - @JsonIgnore + @JsonProperty("punctuate") public Optional getPunctuate() { return punctuate; } @@ -442,7 +441,7 @@ public Optional getPunctuate() { /** * @return Redaction removes sensitive information from your transcripts */ - @JsonIgnore + @JsonProperty("redact") public Optional getRedact() { return redact; } @@ -450,7 +449,7 @@ public Optional getRedact() { /** * @return Apply formatting to transcript output. When set to true, additional formatting will be applied to transcripts to improve readability */ - @JsonIgnore + @JsonProperty("smart_format") public Optional getSmartFormat() { return smartFormat; } @@ -458,7 +457,7 @@ public Optional getSmartFormat() { /** * @return Segments speech into meaningful semantic units */ - @JsonIgnore + @JsonProperty("utterances") public Optional getUtterances() { return utterances; } @@ -466,7 +465,7 @@ public Optional getUtterances() { /** * @return Seconds to wait before detecting a pause between words in submitted audio */ - @JsonIgnore + @JsonProperty("utt_split") public Optional getUttSplit() { return uttSplit; } @@ -474,7 +473,7 @@ public Optional getUttSplit() { /** * @return Version of an AI model to use */ - @JsonIgnore + @JsonProperty("version") public Optional getVersion() { return version; } @@ -482,7 +481,7 @@ public Optional getVersion() { /** * @return Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip */ - @JsonIgnore + @JsonProperty("mip_opt_out") public Optional getMipOptOut() { return mipOptOut; } @@ -752,14 +751,14 @@ public interface _FinalStage { _FinalStage detectLanguage(Boolean detectLanguage); /** - *

Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0

+ *

Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0.

*/ _FinalStage diarize(Optional diarize); _FinalStage diarize(Boolean diarize); /** - *

Select and enable a specific batch diarization model version. If specifying this parameter, you should not set the deprecated diarize=true parameter. Not accepted on streaming requests.

+ *

Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set the deprecated diarize=true parameter. For batch, supported values are latest (currently v2), v1, and v2. For streaming, supported values are latest (currently v1) and v1; v2 returns a validation error on streaming requests.

*/ _FinalStage diarizeModel(Optional diarizeModel); @@ -1359,7 +1358,7 @@ public _FinalStage dictation(Optional dictation) { } /** - *

Select and enable a specific batch diarization model version. If specifying this parameter, you should not set the deprecated diarize=true parameter. Not accepted on streaming requests.

+ *

Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set the deprecated diarize=true parameter. For batch, supported values are latest (currently v2), v1, and v2. For streaming, supported values are latest (currently v1) and v1; v2 returns a validation error on streaming requests.

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

Select and enable a specific batch diarization model version. If specifying this parameter, you should not set the deprecated diarize=true parameter. Not accepted on streaming requests.

+ *

Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set the deprecated diarize=true parameter. For batch, supported values are latest (currently v2), v1, and v2. For streaming, supported values are latest (currently v1) and v1; v2 returns a validation error on streaming requests.

*/ @java.lang.Override @JsonSetter(value = "diarize_model", nulls = Nulls.SKIP) @@ -1379,7 +1378,7 @@ public _FinalStage diarizeModel(Optional dia } /** - *

Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0

+ *

Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0.

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

Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0

+ *

Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0.

*/ @java.lang.Override @JsonSetter(value = "diarize", nulls = Nulls.SKIP) diff --git a/src/main/java/com/deepgram/resources/listen/v1/types/DiarizeModel.java b/src/main/java/com/deepgram/resources/listen/v1/types/DiarizeModel.java new file mode 100644 index 0000000..6bc728c --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v1/types/DiarizeModel.java @@ -0,0 +1,82 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v1.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DiarizeModel { + public static final DiarizeModel V1 = new DiarizeModel(Value.V1, "v1"); + + public static final DiarizeModel LATEST = new DiarizeModel(Value.LATEST, "latest"); + + private final Value value; + + private final String string; + + DiarizeModel(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 DiarizeModel && this.string.equals(((DiarizeModel) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case V1: + return visitor.visitV1(); + case LATEST: + return visitor.visitLatest(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DiarizeModel valueOf(String value) { + switch (value) { + case "v1": + return V1; + case "latest": + return LATEST; + default: + return new DiarizeModel(Value.UNKNOWN, value); + } + } + + public enum Value { + LATEST, + + V1, + + UNKNOWN + } + + public interface Visitor { + T visitLatest(); + + T visitV1(); + + T visitUnknown(String unknownType); + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1ConnectOptions.java b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1ConnectOptions.java index 21d28d9..77822f1 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1ConnectOptions.java +++ b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1ConnectOptions.java @@ -4,6 +4,7 @@ package com.deepgram.resources.listen.v1.websocket; import com.deepgram.core.ObjectMappers; +import com.deepgram.resources.listen.v1.types.DiarizeModel; import com.deepgram.types.ListenV1Callback; import com.deepgram.types.ListenV1CallbackMethod; import com.deepgram.types.ListenV1Channels; @@ -59,6 +60,8 @@ public final class V1ConnectOptions { private final Optional diarize; + private final Optional diarizeModel; + private final Optional dictation; private final Optional encoding; @@ -113,6 +116,7 @@ private V1ConnectOptions( Optional channels, Optional detectEntities, Optional diarize, + Optional diarizeModel, Optional dictation, Optional encoding, Optional endpointing, @@ -142,6 +146,7 @@ private V1ConnectOptions( this.channels = channels; this.detectEntities = detectEntities; this.diarize = diarize; + this.diarizeModel = diarizeModel; this.dictation = dictation; this.encoding = encoding; this.endpointing = endpointing; @@ -188,11 +193,22 @@ public Optional getDetectEntities() { return detectEntities; } + /** + * @return Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0. + */ @JsonProperty("diarize") public Optional getDiarize() { return diarize; } + /** + * @return Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set diarize=true. Supported values for streaming: v1, latest. The v2 value is not supported on streaming and returns a validation error. + */ + @JsonProperty("diarize_model") + public Optional getDiarizeModel() { + return diarizeModel; + } + @JsonProperty("dictation") public Optional getDictation() { return dictation; @@ -328,6 +344,7 @@ private boolean equalTo(V1ConnectOptions other) { && channels.equals(other.channels) && detectEntities.equals(other.detectEntities) && diarize.equals(other.diarize) + && diarizeModel.equals(other.diarizeModel) && dictation.equals(other.dictation) && encoding.equals(other.encoding) && endpointing.equals(other.endpointing) @@ -361,6 +378,7 @@ public int hashCode() { this.channels, this.detectEntities, this.diarize, + this.diarizeModel, this.dictation, this.encoding, this.endpointing, @@ -427,10 +445,20 @@ public interface _FinalStage { _FinalStage detectEntities(ListenV1DetectEntities detectEntities); + /** + *

Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0.

+ */ _FinalStage diarize(Optional diarize); _FinalStage diarize(ListenV1Diarize diarize); + /** + *

Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set diarize=true. Supported values for streaming: v1, latest. The v2 value is not supported on streaming and returns a validation error.

+ */ + _FinalStage diarizeModel(Optional diarizeModel); + + _FinalStage diarizeModel(DiarizeModel diarizeModel); + _FinalStage dictation(Optional dictation); _FinalStage dictation(ListenV1Dictation dictation); @@ -568,6 +596,8 @@ public static final class Builder implements ModelStage, _FinalStage { private Optional dictation = Optional.empty(); + private Optional diarizeModel = Optional.empty(); + private Optional diarize = Optional.empty(); private Optional detectEntities = Optional.empty(); @@ -590,6 +620,7 @@ public Builder from(V1ConnectOptions other) { channels(other.getChannels()); detectEntities(other.getDetectEntities()); diarize(other.getDiarize()); + diarizeModel(other.getDiarizeModel()); dictation(other.getDictation()); encoding(other.getEncoding()); endpointing(other.getEndpointing()); @@ -914,12 +945,39 @@ public _FinalStage dictation(Optional dictation) { return this; } + /** + *

Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set diarize=true. Supported values for streaming: v1, latest. The v2 value is not supported on streaming and returns a validation error.

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

Select and enable a specific diarization model version. Specifying this parameter enables diarization and selects the model — you do not need to also set diarize=true. Supported values for streaming: v1, latest. The v2 value is not supported on streaming and returns a validation error.

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

Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0.

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

Deprecated: use diarize_model instead. Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0.

+ */ @java.lang.Override @JsonSetter(value = "diarize", nulls = Nulls.SKIP) public _FinalStage diarize(Optional diarize) { @@ -987,6 +1045,7 @@ public V1ConnectOptions build() { channels, detectEntities, diarize, + diarizeModel, dictation, encoding, endpointing, 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 c3e4af4..4952808 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 @@ -7,6 +7,7 @@ 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.listen.v1.types.ListenV1CloseStream; import com.deepgram.resources.listen.v1.types.ListenV1Finalize; @@ -120,6 +121,10 @@ public CompletableFuture connect(V1ConnectOptions options) { 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())); @@ -212,7 +217,7 @@ public CompletableFuture connect(V1ConnectOptions options) { "version", String.valueOf(options.getVersion().get())); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers(null).forEach(requestBuilder::addHeader); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); this.readyState = WebSocketReadyState.CONNECTING; ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null 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 ccc1104..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 @@ -9,22 +9,16 @@ 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 = ListenV2CloseStream.Builder.class) public final class ListenV2CloseStream { - private final ListenV2CloseStreamType type; - private final Map additionalProperties; - private ListenV2CloseStream(ListenV2CloseStreamType type, Map additionalProperties) { - this.type = type; + private ListenV2CloseStream(Map additionalProperties) { this.additionalProperties = additionalProperties; } @@ -32,14 +26,14 @@ private ListenV2CloseStream(ListenV2CloseStreamType type, Map ad * @return Message type identifier */ @JsonProperty("type") - public ListenV2CloseStreamType getType() { - return type; + public String getType() { + return "CloseStream"; } @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof ListenV2CloseStream && equalTo((ListenV2CloseStream) other); + return other instanceof ListenV2CloseStream; } @JsonAnyGetter @@ -47,80 +41,35 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(ListenV2CloseStream other) { - return type.equals(other.type); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.type); - } - @java.lang.Override public String toString() { return ObjectMappers.stringify(this); } - public static TypeStage builder() { + public static Builder builder() { return new Builder(); } - public interface TypeStage { - /** - *

Message type identifier

- */ - _FinalStage type(@NotNull ListenV2CloseStreamType type); - - Builder from(ListenV2CloseStream other); - } - - public interface _FinalStage { - ListenV2CloseStream build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - } - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements TypeStage, _FinalStage { - private ListenV2CloseStreamType type; - + public static final class Builder { @JsonAnySetter private Map additionalProperties = new HashMap<>(); private Builder() {} - @java.lang.Override public Builder from(ListenV2CloseStream other) { - type(other.getType()); - return this; - } - - /** - *

Message type identifier

- *

Message type identifier

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("type") - public _FinalStage type(@NotNull ListenV2CloseStreamType type) { - this.type = Objects.requireNonNull(type, "type must not be null"); return this; } - @java.lang.Override public ListenV2CloseStream build() { - return new ListenV2CloseStream(type, additionalProperties); + return new ListenV2CloseStream(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/types/ListenV2CloseStreamType.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStreamType.java deleted file mode 100644 index 7882b21..0000000 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStreamType.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.listen.v2.types; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public final class ListenV2CloseStreamType { - public static final ListenV2CloseStreamType CLOSE_STREAM = - new ListenV2CloseStreamType(Value.CLOSE_STREAM, "CloseStream"); - - public static final ListenV2CloseStreamType KEEP_ALIVE = new ListenV2CloseStreamType(Value.KEEP_ALIVE, "KeepAlive"); - - public static final ListenV2CloseStreamType FINALIZE = new ListenV2CloseStreamType(Value.FINALIZE, "Finalize"); - - private final Value value; - - private final String string; - - ListenV2CloseStreamType(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 ListenV2CloseStreamType - && this.string.equals(((ListenV2CloseStreamType) other).string)); - } - - @java.lang.Override - public int hashCode() { - return this.string.hashCode(); - } - - public T visit(Visitor visitor) { - switch (value) { - case CLOSE_STREAM: - return visitor.visitCloseStream(); - case KEEP_ALIVE: - return visitor.visitKeepAlive(); - case FINALIZE: - return visitor.visitFinalize(); - case UNKNOWN: - default: - return visitor.visitUnknown(string); - } - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static ListenV2CloseStreamType valueOf(String value) { - switch (value) { - case "CloseStream": - return CLOSE_STREAM; - case "KeepAlive": - return KEEP_ALIVE; - case "Finalize": - return FINALIZE; - default: - return new ListenV2CloseStreamType(Value.UNKNOWN, value); - } - } - - public enum Value { - FINALIZE, - - CLOSE_STREAM, - - KEEP_ALIVE, - - UNKNOWN - } - - public interface Visitor { - T visitFinalize(); - - T visitCloseStream(); - - T visitKeepAlive(); - - T visitUnknown(String unknownType); - } -} diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfoWordsItem.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfoWordsItem.java index b7a751e..62a607b 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfoWordsItem.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfoWordsItem.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 ListenV2TurnInfoWordsItem { private final float confidence; + private final Optional start; + + private final Optional end; + private final Map additionalProperties; - private ListenV2TurnInfoWordsItem(String word, float confidence, Map additionalProperties) { + private ListenV2TurnInfoWordsItem( + String word, + float confidence, + Optional start, + Optional end, + Map additionalProperties) { this.word = word; this.confidence = confidence; + this.start = start; + this.end = end; this.additionalProperties = additionalProperties; } @@ -47,6 +60,22 @@ public float getConfidence() { return confidence; } + /** + * @return The start time of the word + */ + @JsonProperty("start") + public Optional getStart() { + return start; + } + + /** + * @return The end time of the word + */ + @JsonProperty("end") + public Optional getEnd() { + return end; + } + @java.lang.Override public boolean equals(Object other) { if (this == other) return true; @@ -59,12 +88,15 @@ public Map getAdditionalProperties() { } private boolean equalTo(ListenV2TurnInfoWordsItem other) { - return word.equals(other.word) && confidence == other.confidence; + return word.equals(other.word) + && confidence == other.confidence + && start.equals(other.start) + && end.equals(other.end); } @java.lang.Override public int hashCode() { - return Objects.hash(this.word, this.confidence); + return Objects.hash(this.word, this.confidence, this.start, this.end); } @java.lang.Override @@ -98,6 +130,20 @@ public interface _FinalStage { _FinalStage additionalProperty(String key, Object value); _FinalStage additionalProperties(Map additionalProperties); + + /** + *

The start time of the word

+ */ + _FinalStage start(Optional start); + + _FinalStage start(Float start); + + /** + *

The end time of the word

+ */ + _FinalStage end(Optional end); + + _FinalStage end(Float end); } @JsonIgnoreProperties(ignoreUnknown = true) @@ -106,6 +152,10 @@ public static final class Builder implements WordStage, ConfidenceStage, _FinalS private float confidence; + private Optional end = Optional.empty(); + + private Optional start = Optional.empty(); + @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -115,6 +165,8 @@ private Builder() {} public Builder from(ListenV2TurnInfoWordsItem other) { word(other.getWord()); confidence(other.getConfidence()); + start(other.getStart()); + end(other.getEnd()); return this; } @@ -142,9 +194,49 @@ public _FinalStage confidence(float confidence) { return this; } + /** + *

The end time of the word

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

The end time of the word

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

The start time of the word

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

The start time of the word

+ */ + @java.lang.Override + @JsonSetter(value = "start", nulls = Nulls.SKIP) + public _FinalStage start(Optional start) { + this.start = start; + return this; + } + @java.lang.Override public ListenV2TurnInfoWordsItem build() { - return new ListenV2TurnInfoWordsItem(word, confidence, additionalProperties); + return new ListenV2TurnInfoWordsItem(word, confidence, start, end, additionalProperties); } @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2ConnectOptions.java b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2ConnectOptions.java index e2878a5..50cb3bf 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2ConnectOptions.java +++ b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2ConnectOptions.java @@ -12,6 +12,7 @@ import com.deepgram.types.ListenV2LanguageHint; import com.deepgram.types.ListenV2MipOptOut; import com.deepgram.types.ListenV2Model; +import com.deepgram.types.ListenV2ProfanityFilter; import com.deepgram.types.ListenV2SampleRate; import com.deepgram.types.ListenV2Tag; import com.fasterxml.jackson.annotation.JsonAnyGetter; @@ -47,6 +48,8 @@ public final class V2ConnectOptions { private final Optional languageHint; + private final Optional profanityFilter; + private final Optional mipOptOut; private final Optional tag; @@ -62,6 +65,7 @@ private V2ConnectOptions( Optional eotTimeoutMs, Optional keyterm, Optional languageHint, + Optional profanityFilter, Optional mipOptOut, Optional tag, Map additionalProperties) { @@ -73,6 +77,7 @@ private V2ConnectOptions( this.eotTimeoutMs = eotTimeoutMs; this.keyterm = keyterm; this.languageHint = languageHint; + this.profanityFilter = profanityFilter; this.mipOptOut = mipOptOut; this.tag = tag; this.additionalProperties = additionalProperties; @@ -118,6 +123,11 @@ public Optional getLanguageHint() { return languageHint; } + @JsonProperty("profanity_filter") + public Optional getProfanityFilter() { + return profanityFilter; + } + @JsonProperty("mip_opt_out") public Optional getMipOptOut() { return mipOptOut; @@ -148,6 +158,7 @@ private boolean equalTo(V2ConnectOptions other) { && eotTimeoutMs.equals(other.eotTimeoutMs) && keyterm.equals(other.keyterm) && languageHint.equals(other.languageHint) + && profanityFilter.equals(other.profanityFilter) && mipOptOut.equals(other.mipOptOut) && tag.equals(other.tag); } @@ -163,6 +174,7 @@ public int hashCode() { this.eotTimeoutMs, this.keyterm, this.languageHint, + this.profanityFilter, this.mipOptOut, this.tag); } @@ -217,6 +229,10 @@ public interface _FinalStage { _FinalStage languageHint(ListenV2LanguageHint languageHint); + _FinalStage profanityFilter(Optional profanityFilter); + + _FinalStage profanityFilter(ListenV2ProfanityFilter profanityFilter); + _FinalStage mipOptOut(Optional mipOptOut); _FinalStage mipOptOut(ListenV2MipOptOut mipOptOut); @@ -234,6 +250,8 @@ public static final class Builder implements ModelStage, _FinalStage { private Optional mipOptOut = Optional.empty(); + private Optional profanityFilter = Optional.empty(); + private Optional languageHint = Optional.empty(); private Optional keyterm = Optional.empty(); @@ -263,6 +281,7 @@ public Builder from(V2ConnectOptions other) { eotTimeoutMs(other.getEotTimeoutMs()); keyterm(other.getKeyterm()); languageHint(other.getLanguageHint()); + profanityFilter(other.getProfanityFilter()); mipOptOut(other.getMipOptOut()); tag(other.getTag()); return this; @@ -301,6 +320,19 @@ public _FinalStage mipOptOut(Optional mipOptOut) { return this; } + @java.lang.Override + public _FinalStage profanityFilter(ListenV2ProfanityFilter profanityFilter) { + this.profanityFilter = Optional.ofNullable(profanityFilter); + return this; + } + + @java.lang.Override + @JsonSetter(value = "profanity_filter", nulls = Nulls.SKIP) + public _FinalStage profanityFilter(Optional profanityFilter) { + this.profanityFilter = profanityFilter; + return this; + } + @java.lang.Override public _FinalStage languageHint(ListenV2LanguageHint languageHint) { this.languageHint = Optional.ofNullable(languageHint); @@ -403,6 +435,7 @@ public V2ConnectOptions build() { eotTimeoutMs, keyterm, languageHint, + profanityFilter, mipOptOut, tag, additionalProperties); 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 e2244d5..51279df 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 @@ -7,6 +7,7 @@ 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.listen.v2.types.ListenV2CloseStream; import com.deepgram.resources.listen.v2.types.ListenV2Configure; @@ -131,6 +132,11 @@ public CompletableFuture connect(V2ConnectOptions options) { urlBuilder.addQueryParameter( "language_hint", String.valueOf(options.getLanguageHint().get())); } + if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { + urlBuilder.addQueryParameter( + "profanity_filter", + String.valueOf(options.getProfanityFilter().get())); + } if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { urlBuilder.addQueryParameter( "mip_opt_out", String.valueOf(options.getMipOptOut().get())); @@ -139,7 +145,7 @@ public CompletableFuture connect(V2ConnectOptions options) { urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers(null).forEach(requestBuilder::addHeader); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); this.readyState = WebSocketReadyState.CONNECTING; ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null diff --git a/src/main/java/com/deepgram/resources/manage/v1/models/requests/ModelsListRequest.java b/src/main/java/com/deepgram/resources/manage/v1/models/requests/ModelsListRequest.java index d5e17bc..86fd894 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/models/requests/ModelsListRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/models/requests/ModelsListRequest.java @@ -6,9 +6,9 @@ import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -32,7 +32,7 @@ private ModelsListRequest(Optional includeOutdated, Map /** * @return

non-latest versions of models

*/ - @JsonIgnore + @JsonProperty("include_outdated") public Optional getIncludeOutdated() { return includeOutdated; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/billing/breakdown/requests/BreakdownListRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/billing/breakdown/requests/BreakdownListRequest.java index eb39f2b..206da0f 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/billing/breakdown/requests/BreakdownListRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/billing/breakdown/requests/BreakdownListRequest.java @@ -8,9 +8,9 @@ import com.deepgram.resources.manage.v1.projects.billing.breakdown.types.BreakdownListRequestGroupingItem; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -62,7 +62,7 @@ private BreakdownListRequest( /** * @return Group billing breakdown by one or more dimensions (accessor, deployment, line_item, tags) */ - @JsonIgnore + @JsonProperty("grouping") public Optional> getGrouping() { return grouping; } @@ -70,7 +70,7 @@ public Optional> getGrouping() { /** * @return Start date of the requested date range. Format accepted is YYYY-MM-DD */ - @JsonIgnore + @JsonProperty("start") public Optional getStart() { return start; } @@ -78,7 +78,7 @@ public Optional getStart() { /** * @return End date of the requested date range. Format accepted is YYYY-MM-DD */ - @JsonIgnore + @JsonProperty("end") public Optional getEnd() { return end; } @@ -86,7 +86,7 @@ public Optional getEnd() { /** * @return Filter for requests where a specific accessor was used */ - @JsonIgnore + @JsonProperty("accessor") public Optional getAccessor() { return accessor; } @@ -94,7 +94,7 @@ public Optional getAccessor() { /** * @return Filter for requests where a specific deployment was used */ - @JsonIgnore + @JsonProperty("deployment") public Optional getDeployment() { return deployment; } @@ -102,7 +102,7 @@ public Optional getDeployment() { /** * @return Filter for requests where a specific tag was used */ - @JsonIgnore + @JsonProperty("tag") public Optional getTag() { return tag; } @@ -110,7 +110,7 @@ public Optional getTag() { /** * @return Filter requests by line item (e.g. streaming::nova-3) */ - @JsonIgnore + @JsonProperty("line_item") public Optional getLineItem() { return lineItem; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/billing/fields/requests/FieldsListRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/billing/fields/requests/FieldsListRequest.java index 6043aeb..08077bc 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/billing/fields/requests/FieldsListRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/billing/fields/requests/FieldsListRequest.java @@ -6,9 +6,9 @@ import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -35,7 +35,7 @@ private FieldsListRequest(Optional start, Optional end, Map getStart() { return start; } @@ -43,7 +43,7 @@ public Optional getStart() { /** * @return End date of the requested date range. Format accepted is YYYY-MM-DD */ - @JsonIgnore + @JsonProperty("end") public Optional getEnd() { return end; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/billing/purchases/requests/PurchasesListRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/billing/purchases/requests/PurchasesListRequest.java index 891502b..840b01a 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/billing/purchases/requests/PurchasesListRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/billing/purchases/requests/PurchasesListRequest.java @@ -6,9 +6,9 @@ import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -32,7 +32,7 @@ private PurchasesListRequest(Optional limit, Map additio /** * @return Number of results to return per page. Default 10. Range [1,1000] */ - @JsonIgnore + @JsonProperty("limit") public Optional getLimit() { return limit; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/keys/requests/KeysListRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/keys/requests/KeysListRequest.java index d18d7fb..6c7fe15 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/keys/requests/KeysListRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/keys/requests/KeysListRequest.java @@ -7,9 +7,9 @@ import com.deepgram.resources.manage.v1.projects.keys.types.KeysListRequestStatus; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -33,7 +33,7 @@ private KeysListRequest(Optional status, Map getStatus() { return status; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/models/requests/ModelsListRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/models/requests/ModelsListRequest.java index ae16285..a82d324 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/models/requests/ModelsListRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/models/requests/ModelsListRequest.java @@ -6,9 +6,9 @@ import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -32,7 +32,7 @@ private ModelsListRequest(Optional includeOutdated, Map /** * @return

non-latest versions of models

*/ - @JsonIgnore + @JsonProperty("include_outdated") public Optional getIncludeOutdated() { return includeOutdated; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/requests/ProjectsGetRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/requests/ProjectsGetRequest.java index 46bf0f7..c61e56d 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/requests/ProjectsGetRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/requests/ProjectsGetRequest.java @@ -6,9 +6,9 @@ import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -36,7 +36,7 @@ private ProjectsGetRequest( /** * @return Number of results to return per page. Default 10. Range [1,1000] */ - @JsonIgnore + @JsonProperty("limit") public Optional getLimit() { return limit; } @@ -44,7 +44,7 @@ public Optional getLimit() { /** * @return Navigate and return the results to retrieve specific portions of information of the response */ - @JsonIgnore + @JsonProperty("page") public Optional getPage() { return page; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/requests/requests/RequestsListRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/requests/requests/RequestsListRequest.java index ec38331..9463715 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/requests/requests/RequestsListRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/requests/requests/RequestsListRequest.java @@ -10,9 +10,9 @@ import com.deepgram.resources.manage.v1.projects.requests.types.RequestsListRequestStatus; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -75,7 +75,7 @@ private RequestsListRequest( /** * @return Start date of the requested date range. Formats accepted are YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, or YYYY-MM-DDTHH:MM:SS+HH:MM */ - @JsonIgnore + @JsonProperty("start") public Optional getStart() { return start; } @@ -83,7 +83,7 @@ public Optional getStart() { /** * @return End date of the requested date range. Formats accepted are YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, or YYYY-MM-DDTHH:MM:SS+HH:MM */ - @JsonIgnore + @JsonProperty("end") public Optional getEnd() { return end; } @@ -91,7 +91,7 @@ public Optional getEnd() { /** * @return Number of results to return per page. Default 10. Range [1,1000] */ - @JsonIgnore + @JsonProperty("limit") public Optional getLimit() { return limit; } @@ -99,7 +99,7 @@ public Optional getLimit() { /** * @return Navigate and return the results to retrieve specific portions of information of the response */ - @JsonIgnore + @JsonProperty("page") public Optional getPage() { return page; } @@ -107,7 +107,7 @@ public Optional getPage() { /** * @return Filter for requests where a specific accessor was used */ - @JsonIgnore + @JsonProperty("accessor") public Optional getAccessor() { return accessor; } @@ -115,7 +115,7 @@ public Optional getAccessor() { /** * @return Filter for a specific request id */ - @JsonIgnore + @JsonProperty("request_id") public Optional getRequestId() { return requestId; } @@ -123,7 +123,7 @@ public Optional getRequestId() { /** * @return Filter for requests where a specific deployment was used */ - @JsonIgnore + @JsonProperty("deployment") public Optional getDeployment() { return deployment; } @@ -131,7 +131,7 @@ public Optional getDeployment() { /** * @return Filter for requests where a specific endpoint was used */ - @JsonIgnore + @JsonProperty("endpoint") public Optional getEndpoint() { return endpoint; } @@ -139,7 +139,7 @@ public Optional getEndpoint() { /** * @return Filter for requests where a specific method was used */ - @JsonIgnore + @JsonProperty("method") public Optional getMethod() { return method; } @@ -147,7 +147,7 @@ public Optional getMethod() { /** * @return Filter for requests that succeeded (status code < 300) or failed (status code >=400) */ - @JsonIgnore + @JsonProperty("status") public Optional getStatus() { return status; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/usage/breakdown/requests/BreakdownGetRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/usage/breakdown/requests/BreakdownGetRequest.java index 912cb6a..397e1f4 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/usage/breakdown/requests/BreakdownGetRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/usage/breakdown/requests/BreakdownGetRequest.java @@ -10,9 +10,9 @@ import com.deepgram.resources.manage.v1.projects.usage.breakdown.types.BreakdownGetRequestMethod; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -214,7 +214,7 @@ private BreakdownGetRequest( /** * @return Start date of the requested date range. Format accepted is YYYY-MM-DD */ - @JsonIgnore + @JsonProperty("start") public Optional getStart() { return start; } @@ -222,7 +222,7 @@ public Optional getStart() { /** * @return End date of the requested date range. Format accepted is YYYY-MM-DD */ - @JsonIgnore + @JsonProperty("end") public Optional getEnd() { return end; } @@ -230,7 +230,7 @@ public Optional getEnd() { /** * @return Common usage grouping parameters */ - @JsonIgnore + @JsonProperty("grouping") public Optional getGrouping() { return grouping; } @@ -238,7 +238,7 @@ public Optional getGrouping() { /** * @return Filter for requests where a specific accessor was used */ - @JsonIgnore + @JsonProperty("accessor") public Optional getAccessor() { return accessor; } @@ -246,7 +246,7 @@ public Optional getAccessor() { /** * @return Filter for requests where alternatives were used */ - @JsonIgnore + @JsonProperty("alternatives") public Optional getAlternatives() { return alternatives; } @@ -254,7 +254,7 @@ public Optional getAlternatives() { /** * @return Filter for requests where callback method was used */ - @JsonIgnore + @JsonProperty("callback_method") public Optional getCallbackMethod() { return callbackMethod; } @@ -262,7 +262,7 @@ public Optional getCallbackMethod() { /** * @return Filter for requests where callback was used */ - @JsonIgnore + @JsonProperty("callback") public Optional getCallback() { return callback; } @@ -270,7 +270,7 @@ public Optional getCallback() { /** * @return Filter for requests where channels were used */ - @JsonIgnore + @JsonProperty("channels") public Optional getChannels() { return channels; } @@ -278,7 +278,7 @@ public Optional getChannels() { /** * @return Filter for requests where custom intent mode was used */ - @JsonIgnore + @JsonProperty("custom_intent_mode") public Optional getCustomIntentMode() { return customIntentMode; } @@ -286,7 +286,7 @@ public Optional getCustomIntentMode() { /** * @return Filter for requests where custom intent was used */ - @JsonIgnore + @JsonProperty("custom_intent") public Optional getCustomIntent() { return customIntent; } @@ -294,7 +294,7 @@ public Optional getCustomIntent() { /** * @return Filter for requests where custom topic mode was used */ - @JsonIgnore + @JsonProperty("custom_topic_mode") public Optional getCustomTopicMode() { return customTopicMode; } @@ -302,7 +302,7 @@ public Optional getCustomTopicMode() { /** * @return Filter for requests where custom topic was used */ - @JsonIgnore + @JsonProperty("custom_topic") public Optional getCustomTopic() { return customTopic; } @@ -310,7 +310,7 @@ public Optional getCustomTopic() { /** * @return Filter for requests where a specific deployment was used */ - @JsonIgnore + @JsonProperty("deployment") public Optional getDeployment() { return deployment; } @@ -318,7 +318,7 @@ public Optional getDeployment() { /** * @return Filter for requests where detect entities was used */ - @JsonIgnore + @JsonProperty("detect_entities") public Optional getDetectEntities() { return detectEntities; } @@ -326,7 +326,7 @@ public Optional getDetectEntities() { /** * @return Filter for requests where detect language was used */ - @JsonIgnore + @JsonProperty("detect_language") public Optional getDetectLanguage() { return detectLanguage; } @@ -334,7 +334,7 @@ public Optional getDetectLanguage() { /** * @return Filter for requests where diarize was used */ - @JsonIgnore + @JsonProperty("diarize") public Optional getDiarize() { return diarize; } @@ -342,7 +342,7 @@ public Optional getDiarize() { /** * @return Filter for requests where dictation was used */ - @JsonIgnore + @JsonProperty("dictation") public Optional getDictation() { return dictation; } @@ -350,7 +350,7 @@ public Optional getDictation() { /** * @return Filter for requests where encoding was used */ - @JsonIgnore + @JsonProperty("encoding") public Optional getEncoding() { return encoding; } @@ -358,7 +358,7 @@ public Optional getEncoding() { /** * @return Filter for requests where a specific endpoint was used */ - @JsonIgnore + @JsonProperty("endpoint") public Optional getEndpoint() { return endpoint; } @@ -366,7 +366,7 @@ public Optional getEndpoint() { /** * @return Filter for requests where extra was used */ - @JsonIgnore + @JsonProperty("extra") public Optional getExtra() { return extra; } @@ -374,7 +374,7 @@ public Optional getExtra() { /** * @return Filter for requests where filler words was used */ - @JsonIgnore + @JsonProperty("filler_words") public Optional getFillerWords() { return fillerWords; } @@ -382,7 +382,7 @@ public Optional getFillerWords() { /** * @return Filter for requests where intents was used */ - @JsonIgnore + @JsonProperty("intents") public Optional getIntents() { return intents; } @@ -390,7 +390,7 @@ public Optional getIntents() { /** * @return Filter for requests where keyterm was used */ - @JsonIgnore + @JsonProperty("keyterm") public Optional getKeyterm() { return keyterm; } @@ -398,7 +398,7 @@ public Optional getKeyterm() { /** * @return Filter for requests where keywords was used */ - @JsonIgnore + @JsonProperty("keywords") public Optional getKeywords() { return keywords; } @@ -406,7 +406,7 @@ public Optional getKeywords() { /** * @return Filter for requests where language was used */ - @JsonIgnore + @JsonProperty("language") public Optional getLanguage() { return language; } @@ -414,7 +414,7 @@ public Optional getLanguage() { /** * @return Filter for requests where measurements were used */ - @JsonIgnore + @JsonProperty("measurements") public Optional getMeasurements() { return measurements; } @@ -422,7 +422,7 @@ public Optional getMeasurements() { /** * @return Filter for requests where a specific method was used */ - @JsonIgnore + @JsonProperty("method") public Optional getMethod() { return method; } @@ -430,7 +430,7 @@ public Optional getMethod() { /** * @return Filter for requests where a specific model uuid was used */ - @JsonIgnore + @JsonProperty("model") public Optional getModel() { return model; } @@ -438,7 +438,7 @@ public Optional getModel() { /** * @return Filter for requests where multichannel was used */ - @JsonIgnore + @JsonProperty("multichannel") public Optional getMultichannel() { return multichannel; } @@ -446,7 +446,7 @@ public Optional getMultichannel() { /** * @return Filter for requests where numerals were used */ - @JsonIgnore + @JsonProperty("numerals") public Optional getNumerals() { return numerals; } @@ -454,7 +454,7 @@ public Optional getNumerals() { /** * @return Filter for requests where paragraphs were used */ - @JsonIgnore + @JsonProperty("paragraphs") public Optional getParagraphs() { return paragraphs; } @@ -462,7 +462,7 @@ public Optional getParagraphs() { /** * @return Filter for requests where profanity filter was used */ - @JsonIgnore + @JsonProperty("profanity_filter") public Optional getProfanityFilter() { return profanityFilter; } @@ -470,7 +470,7 @@ public Optional getProfanityFilter() { /** * @return Filter for requests where punctuate was used */ - @JsonIgnore + @JsonProperty("punctuate") public Optional getPunctuate() { return punctuate; } @@ -478,7 +478,7 @@ public Optional getPunctuate() { /** * @return Filter for requests where redact was used */ - @JsonIgnore + @JsonProperty("redact") public Optional getRedact() { return redact; } @@ -486,7 +486,7 @@ public Optional getRedact() { /** * @return Filter for requests where replace was used */ - @JsonIgnore + @JsonProperty("replace") public Optional getReplace() { return replace; } @@ -494,7 +494,7 @@ public Optional getReplace() { /** * @return Filter for requests where sample rate was used */ - @JsonIgnore + @JsonProperty("sample_rate") public Optional getSampleRate() { return sampleRate; } @@ -502,7 +502,7 @@ public Optional getSampleRate() { /** * @return Filter for requests where search was used */ - @JsonIgnore + @JsonProperty("search") public Optional getSearch() { return search; } @@ -510,7 +510,7 @@ public Optional getSearch() { /** * @return Filter for requests where sentiment was used */ - @JsonIgnore + @JsonProperty("sentiment") public Optional getSentiment() { return sentiment; } @@ -518,7 +518,7 @@ public Optional getSentiment() { /** * @return Filter for requests where smart format was used */ - @JsonIgnore + @JsonProperty("smart_format") public Optional getSmartFormat() { return smartFormat; } @@ -526,7 +526,7 @@ public Optional getSmartFormat() { /** * @return Filter for requests where summarize was used */ - @JsonIgnore + @JsonProperty("summarize") public Optional getSummarize() { return summarize; } @@ -534,7 +534,7 @@ public Optional getSummarize() { /** * @return Filter for requests where a specific tag was used */ - @JsonIgnore + @JsonProperty("tag") public Optional getTag() { return tag; } @@ -542,7 +542,7 @@ public Optional getTag() { /** * @return Filter for requests where topics was used */ - @JsonIgnore + @JsonProperty("topics") public Optional getTopics() { return topics; } @@ -550,7 +550,7 @@ public Optional getTopics() { /** * @return Filter for requests where utt split was used */ - @JsonIgnore + @JsonProperty("utt_split") public Optional getUttSplit() { return uttSplit; } @@ -558,7 +558,7 @@ public Optional getUttSplit() { /** * @return Filter for requests where utterances was used */ - @JsonIgnore + @JsonProperty("utterances") public Optional getUtterances() { return utterances; } @@ -566,7 +566,7 @@ public Optional getUtterances() { /** * @return Filter for requests where version was used */ - @JsonIgnore + @JsonProperty("version") public Optional getVersion() { return version; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/usage/fields/requests/FieldsListRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/usage/fields/requests/FieldsListRequest.java index 01b4d32..0501df7 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/usage/fields/requests/FieldsListRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/usage/fields/requests/FieldsListRequest.java @@ -6,9 +6,9 @@ import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -35,7 +35,7 @@ private FieldsListRequest(Optional start, Optional end, Map getStart() { return start; } @@ -43,7 +43,7 @@ public Optional getStart() { /** * @return End date of the requested date range. Format accepted is YYYY-MM-DD */ - @JsonIgnore + @JsonProperty("end") public Optional getEnd() { return end; } diff --git a/src/main/java/com/deepgram/resources/manage/v1/projects/usage/requests/UsageGetRequest.java b/src/main/java/com/deepgram/resources/manage/v1/projects/usage/requests/UsageGetRequest.java index b4c6d0a..31371f6 100644 --- a/src/main/java/com/deepgram/resources/manage/v1/projects/usage/requests/UsageGetRequest.java +++ b/src/main/java/com/deepgram/resources/manage/v1/projects/usage/requests/UsageGetRequest.java @@ -9,9 +9,9 @@ import com.deepgram.resources.manage.v1.projects.usage.types.UsageGetRequestMethod; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; 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; @@ -209,7 +209,7 @@ private UsageGetRequest( /** * @return Start date of the requested date range. Format accepted is YYYY-MM-DD */ - @JsonIgnore + @JsonProperty("start") public Optional getStart() { return start; } @@ -217,7 +217,7 @@ public Optional getStart() { /** * @return End date of the requested date range. Format accepted is YYYY-MM-DD */ - @JsonIgnore + @JsonProperty("end") public Optional getEnd() { return end; } @@ -225,7 +225,7 @@ public Optional getEnd() { /** * @return Filter for requests where a specific accessor was used */ - @JsonIgnore + @JsonProperty("accessor") public Optional getAccessor() { return accessor; } @@ -233,7 +233,7 @@ public Optional getAccessor() { /** * @return Filter for requests where alternatives were used */ - @JsonIgnore + @JsonProperty("alternatives") public Optional getAlternatives() { return alternatives; } @@ -241,7 +241,7 @@ public Optional getAlternatives() { /** * @return Filter for requests where callback method was used */ - @JsonIgnore + @JsonProperty("callback_method") public Optional getCallbackMethod() { return callbackMethod; } @@ -249,7 +249,7 @@ public Optional getCallbackMethod() { /** * @return Filter for requests where callback was used */ - @JsonIgnore + @JsonProperty("callback") public Optional getCallback() { return callback; } @@ -257,7 +257,7 @@ public Optional getCallback() { /** * @return Filter for requests where channels were used */ - @JsonIgnore + @JsonProperty("channels") public Optional getChannels() { return channels; } @@ -265,7 +265,7 @@ public Optional getChannels() { /** * @return Filter for requests where custom intent mode was used */ - @JsonIgnore + @JsonProperty("custom_intent_mode") public Optional getCustomIntentMode() { return customIntentMode; } @@ -273,7 +273,7 @@ public Optional getCustomIntentMode() { /** * @return Filter for requests where custom intent was used */ - @JsonIgnore + @JsonProperty("custom_intent") public Optional getCustomIntent() { return customIntent; } @@ -281,7 +281,7 @@ public Optional getCustomIntent() { /** * @return Filter for requests where custom topic mode was used */ - @JsonIgnore + @JsonProperty("custom_topic_mode") public Optional getCustomTopicMode() { return customTopicMode; } @@ -289,7 +289,7 @@ public Optional getCustomTopicMode() { /** * @return Filter for requests where custom topic was used */ - @JsonIgnore + @JsonProperty("custom_topic") public Optional getCustomTopic() { return customTopic; } @@ -297,7 +297,7 @@ public Optional getCustomTopic() { /** * @return Filter for requests where a specific deployment was used */ - @JsonIgnore + @JsonProperty("deployment") public Optional getDeployment() { return deployment; } @@ -305,7 +305,7 @@ public Optional getDeployment() { /** * @return Filter for requests where detect entities was used */ - @JsonIgnore + @JsonProperty("detect_entities") public Optional getDetectEntities() { return detectEntities; } @@ -313,7 +313,7 @@ public Optional getDetectEntities() { /** * @return Filter for requests where detect language was used */ - @JsonIgnore + @JsonProperty("detect_language") public Optional getDetectLanguage() { return detectLanguage; } @@ -321,7 +321,7 @@ public Optional getDetectLanguage() { /** * @return Filter for requests where diarize was used */ - @JsonIgnore + @JsonProperty("diarize") public Optional getDiarize() { return diarize; } @@ -329,7 +329,7 @@ public Optional getDiarize() { /** * @return Filter for requests where dictation was used */ - @JsonIgnore + @JsonProperty("dictation") public Optional getDictation() { return dictation; } @@ -337,7 +337,7 @@ public Optional getDictation() { /** * @return Filter for requests where encoding was used */ - @JsonIgnore + @JsonProperty("encoding") public Optional getEncoding() { return encoding; } @@ -345,7 +345,7 @@ public Optional getEncoding() { /** * @return Filter for requests where a specific endpoint was used */ - @JsonIgnore + @JsonProperty("endpoint") public Optional getEndpoint() { return endpoint; } @@ -353,7 +353,7 @@ public Optional getEndpoint() { /** * @return Filter for requests where extra was used */ - @JsonIgnore + @JsonProperty("extra") public Optional getExtra() { return extra; } @@ -361,7 +361,7 @@ public Optional getExtra() { /** * @return Filter for requests where filler words was used */ - @JsonIgnore + @JsonProperty("filler_words") public Optional getFillerWords() { return fillerWords; } @@ -369,7 +369,7 @@ public Optional getFillerWords() { /** * @return Filter for requests where intents was used */ - @JsonIgnore + @JsonProperty("intents") public Optional getIntents() { return intents; } @@ -377,7 +377,7 @@ public Optional getIntents() { /** * @return Filter for requests where keyterm was used */ - @JsonIgnore + @JsonProperty("keyterm") public Optional getKeyterm() { return keyterm; } @@ -385,7 +385,7 @@ public Optional getKeyterm() { /** * @return Filter for requests where keywords was used */ - @JsonIgnore + @JsonProperty("keywords") public Optional getKeywords() { return keywords; } @@ -393,7 +393,7 @@ public Optional getKeywords() { /** * @return Filter for requests where language was used */ - @JsonIgnore + @JsonProperty("language") public Optional getLanguage() { return language; } @@ -401,7 +401,7 @@ public Optional getLanguage() { /** * @return Filter for requests where measurements were used */ - @JsonIgnore + @JsonProperty("measurements") public Optional getMeasurements() { return measurements; } @@ -409,7 +409,7 @@ public Optional getMeasurements() { /** * @return Filter for requests where a specific method was used */ - @JsonIgnore + @JsonProperty("method") public Optional getMethod() { return method; } @@ -417,7 +417,7 @@ public Optional getMethod() { /** * @return Filter for requests where a specific model uuid was used */ - @JsonIgnore + @JsonProperty("model") public Optional getModel() { return model; } @@ -425,7 +425,7 @@ public Optional getModel() { /** * @return Filter for requests where multichannel was used */ - @JsonIgnore + @JsonProperty("multichannel") public Optional getMultichannel() { return multichannel; } @@ -433,7 +433,7 @@ public Optional getMultichannel() { /** * @return Filter for requests where numerals were used */ - @JsonIgnore + @JsonProperty("numerals") public Optional getNumerals() { return numerals; } @@ -441,7 +441,7 @@ public Optional getNumerals() { /** * @return Filter for requests where paragraphs were used */ - @JsonIgnore + @JsonProperty("paragraphs") public Optional getParagraphs() { return paragraphs; } @@ -449,7 +449,7 @@ public Optional getParagraphs() { /** * @return Filter for requests where profanity filter was used */ - @JsonIgnore + @JsonProperty("profanity_filter") public Optional getProfanityFilter() { return profanityFilter; } @@ -457,7 +457,7 @@ public Optional getProfanityFilter() { /** * @return Filter for requests where punctuate was used */ - @JsonIgnore + @JsonProperty("punctuate") public Optional getPunctuate() { return punctuate; } @@ -465,7 +465,7 @@ public Optional getPunctuate() { /** * @return Filter for requests where redact was used */ - @JsonIgnore + @JsonProperty("redact") public Optional getRedact() { return redact; } @@ -473,7 +473,7 @@ public Optional getRedact() { /** * @return Filter for requests where replace was used */ - @JsonIgnore + @JsonProperty("replace") public Optional getReplace() { return replace; } @@ -481,7 +481,7 @@ public Optional getReplace() { /** * @return Filter for requests where sample rate was used */ - @JsonIgnore + @JsonProperty("sample_rate") public Optional getSampleRate() { return sampleRate; } @@ -489,7 +489,7 @@ public Optional getSampleRate() { /** * @return Filter for requests where search was used */ - @JsonIgnore + @JsonProperty("search") public Optional getSearch() { return search; } @@ -497,7 +497,7 @@ public Optional getSearch() { /** * @return Filter for requests where sentiment was used */ - @JsonIgnore + @JsonProperty("sentiment") public Optional getSentiment() { return sentiment; } @@ -505,7 +505,7 @@ public Optional getSentiment() { /** * @return Filter for requests where smart format was used */ - @JsonIgnore + @JsonProperty("smart_format") public Optional getSmartFormat() { return smartFormat; } @@ -513,7 +513,7 @@ public Optional getSmartFormat() { /** * @return Filter for requests where summarize was used */ - @JsonIgnore + @JsonProperty("summarize") public Optional getSummarize() { return summarize; } @@ -521,7 +521,7 @@ public Optional getSummarize() { /** * @return Filter for requests where a specific tag was used */ - @JsonIgnore + @JsonProperty("tag") public Optional getTag() { return tag; } @@ -529,7 +529,7 @@ public Optional getTag() { /** * @return Filter for requests where topics was used */ - @JsonIgnore + @JsonProperty("topics") public Optional getTopics() { return topics; } @@ -537,7 +537,7 @@ public Optional getTopics() { /** * @return Filter for requests where utt split was used */ - @JsonIgnore + @JsonProperty("utt_split") public Optional getUttSplit() { return uttSplit; } @@ -545,7 +545,7 @@ public Optional getUttSplit() { /** * @return Filter for requests where utterances was used */ - @JsonIgnore + @JsonProperty("utterances") public Optional getUtterances() { return utterances; } @@ -553,7 +553,7 @@ public Optional getUtterances() { /** * @return Filter for requests where version was used */ - @JsonIgnore + @JsonProperty("version") public Optional getVersion() { return version; } diff --git a/src/main/java/com/deepgram/resources/read/v1/text/requests/TextAnalyzeRequest.java b/src/main/java/com/deepgram/resources/read/v1/text/requests/TextAnalyzeRequest.java index 29502db..6a42aa7 100644 --- a/src/main/java/com/deepgram/resources/read/v1/text/requests/TextAnalyzeRequest.java +++ b/src/main/java/com/deepgram/resources/read/v1/text/requests/TextAnalyzeRequest.java @@ -11,7 +11,6 @@ import com.deepgram.types.ReadV1Request; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -91,7 +90,7 @@ private TextAnalyzeRequest( /** * @return Label your requests for the purpose of identification during usage reporting */ - @JsonIgnore + @JsonProperty("tag") public Optional> getTag() { return tag; } @@ -99,7 +98,7 @@ public Optional> getTag() { /** * @return Custom topics you want the model to detect within your input audio or text if present Submit up to 100. */ - @JsonIgnore + @JsonProperty("custom_topic") public Optional> getCustomTopic() { return customTopic; } @@ -107,7 +106,7 @@ public Optional> getCustomTopic() { /** * @return Custom intents you want the model to detect within your input audio if present */ - @JsonIgnore + @JsonProperty("custom_intent") public Optional> getCustomIntent() { return customIntent; } @@ -115,7 +114,7 @@ public Optional> getCustomIntent() { /** * @return URL to which we'll make the callback request */ - @JsonIgnore + @JsonProperty("callback") public Optional getCallback() { return callback; } @@ -123,7 +122,7 @@ public Optional getCallback() { /** * @return HTTP method by which the callback request will be made */ - @JsonIgnore + @JsonProperty("callback_method") public Optional getCallbackMethod() { return callbackMethod; } @@ -131,7 +130,7 @@ public Optional getCallbackMethod() { /** * @return Recognizes the sentiment throughout a transcript or text */ - @JsonIgnore + @JsonProperty("sentiment") public Optional getSentiment() { return sentiment; } @@ -139,7 +138,7 @@ public Optional getSentiment() { /** * @return Summarize content. For Listen API, supports string version option. For Read API, accepts boolean only. */ - @JsonIgnore + @JsonProperty("summarize") public Optional getSummarize() { return summarize; } @@ -147,7 +146,7 @@ public Optional getSummarize() { /** * @return Detect topics throughout a transcript or text */ - @JsonIgnore + @JsonProperty("topics") public Optional getTopics() { return topics; } @@ -155,7 +154,7 @@ public Optional getTopics() { /** * @return Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param */ - @JsonIgnore + @JsonProperty("custom_topic_mode") public Optional getCustomTopicMode() { return customTopicMode; } @@ -163,7 +162,7 @@ public Optional getCustomTopicMode() { /** * @return Recognizes speaker intent throughout a transcript or text */ - @JsonIgnore + @JsonProperty("intents") public Optional getIntents() { return intents; } @@ -171,7 +170,7 @@ public Optional getIntents() { /** * @return Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in the custom_intent param. */ - @JsonIgnore + @JsonProperty("custom_intent_mode") public Optional getCustomIntentMode() { return customIntentMode; } @@ -179,7 +178,7 @@ public Optional getCustomIntentMode() { /** * @return The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available */ - @JsonIgnore + @JsonProperty("language") public Optional getLanguage() { return language; } 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 72ef54b..8af4275 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 @@ -7,6 +7,7 @@ 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.speak.v1.types.SpeakV1Clear; import com.deepgram.resources.speak.v1.types.SpeakV1Cleared; @@ -122,7 +123,7 @@ public CompletableFuture connect(V1ConnectOptions options) { "speed", String.valueOf(options.getSpeed().get())); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers(null).forEach(requestBuilder::addHeader); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); this.readyState = WebSocketReadyState.CONNECTING; ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null diff --git a/src/main/java/com/deepgram/types/DeepgramListenProviderV2.java b/src/main/java/com/deepgram/types/DeepgramListenProviderV2.java index 7f76b77..4c0af19 100644 --- a/src/main/java/com/deepgram/types/DeepgramListenProviderV2.java +++ b/src/main/java/com/deepgram/types/DeepgramListenProviderV2.java @@ -26,7 +26,7 @@ public final class DeepgramListenProviderV2 { private final String model; - private final Optional languageHint; + private final Optional> languageHints; private final Optional> keyterms; @@ -35,12 +35,12 @@ public final class DeepgramListenProviderV2 { private DeepgramListenProviderV2( Optional version, String model, - Optional languageHint, + Optional> languageHints, Optional> keyterms, Map additionalProperties) { this.version = version; this.model = model; - this.languageHint = languageHint; + this.languageHints = languageHints; this.keyterms = keyterms; this.additionalProperties = additionalProperties; } @@ -70,11 +70,11 @@ public String getModel() { } /** - * @return One or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details. + * @return An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details. */ - @JsonProperty("language_hint") - public Optional getLanguageHint() { - return languageHint; + @JsonProperty("language_hints") + public Optional> getLanguageHints() { + return languageHints; } /** @@ -99,13 +99,13 @@ public Map getAdditionalProperties() { private boolean equalTo(DeepgramListenProviderV2 other) { return version.equals(other.version) && model.equals(other.model) - && languageHint.equals(other.languageHint) + && languageHints.equals(other.languageHints) && keyterms.equals(other.keyterms); } @java.lang.Override public int hashCode() { - return Objects.hash(this.version, this.model, this.languageHint, this.keyterms); + return Objects.hash(this.version, this.model, this.languageHints, this.keyterms); } @java.lang.Override @@ -141,11 +141,11 @@ public interface _FinalStage { _FinalStage version(String version); /** - *

One or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details.

+ *

An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details.

*/ - _FinalStage languageHint(Optional languageHint); + _FinalStage languageHints(Optional> languageHints); - _FinalStage languageHint(DeepgramListenProviderV2LanguageHint languageHint); + _FinalStage languageHints(List languageHints); /** *

Prompt keyterm recognition to improve Keyword Recall Rate

@@ -161,7 +161,7 @@ public static final class Builder implements ModelStage, _FinalStage { private Optional> keyterms = Optional.empty(); - private Optional languageHint = Optional.empty(); + private Optional> languageHints = Optional.empty(); private Optional version = Optional.empty(); @@ -174,7 +174,7 @@ private Builder() {} public Builder from(DeepgramListenProviderV2 other) { version(other.getVersion()); model(other.getModel()); - languageHint(other.getLanguageHint()); + languageHints(other.getLanguageHints()); keyterms(other.getKeyterms()); return this; } @@ -212,22 +212,22 @@ public _FinalStage keyterms(Optional> keyterms) { } /** - *

One or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details.

+ *

An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details.

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

One or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details.

+ *

An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details.

*/ @java.lang.Override - @JsonSetter(value = "language_hint", nulls = Nulls.SKIP) - public _FinalStage languageHint(Optional languageHint) { - this.languageHint = languageHint; + @JsonSetter(value = "language_hints", nulls = Nulls.SKIP) + public _FinalStage languageHints(Optional> languageHints) { + this.languageHints = languageHints; return this; } @@ -253,7 +253,7 @@ public _FinalStage version(Optional version) { @java.lang.Override public DeepgramListenProviderV2 build() { - return new DeepgramListenProviderV2(version, model, languageHint, keyterms, additionalProperties); + return new DeepgramListenProviderV2(version, model, languageHints, keyterms, additionalProperties); } @java.lang.Override diff --git a/src/main/java/com/deepgram/types/DeepgramListenProviderV2LanguageHint.java b/src/main/java/com/deepgram/types/DeepgramListenProviderV2LanguageHint.java deleted file mode 100644 index cc07718..0000000 --- a/src/main/java/com/deepgram/types/DeepgramListenProviderV2LanguageHint.java +++ /dev/null @@ -1,99 +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.JsonValue; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; -import java.util.List; -import java.util.Objects; - -@JsonDeserialize(using = DeepgramListenProviderV2LanguageHint.Deserializer.class) -public final class DeepgramListenProviderV2LanguageHint { - private final Object value; - - private final int type; - - private DeepgramListenProviderV2LanguageHint(Object value, int type) { - this.value = value; - this.type = type; - } - - @JsonValue - public Object get() { - return this.value; - } - - @SuppressWarnings("unchecked") - public T visit(Visitor visitor) { - if (this.type == 0) { - return visitor.visit((String) this.value); - } else if (this.type == 1) { - return visitor.visit((List) this.value); - } - throw new IllegalStateException("Failed to visit value. This should never happen."); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof DeepgramListenProviderV2LanguageHint - && equalTo((DeepgramListenProviderV2LanguageHint) other); - } - - private boolean equalTo(DeepgramListenProviderV2LanguageHint other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return this.value.toString(); - } - - public static DeepgramListenProviderV2LanguageHint of(String value) { - return new DeepgramListenProviderV2LanguageHint(value, 0); - } - - public static DeepgramListenProviderV2LanguageHint of(List value) { - return new DeepgramListenProviderV2LanguageHint(value, 1); - } - - public interface Visitor { - T visit(String value); - - T visit(List value); - } - - static final class Deserializer extends StdDeserializer { - Deserializer() { - super(DeepgramListenProviderV2LanguageHint.class); - } - - @java.lang.Override - public DeepgramListenProviderV2LanguageHint deserialize(JsonParser p, DeserializationContext context) - throws IOException { - Object value = p.readValueAs(Object.class); - try { - return of(ObjectMappers.JSON_MAPPER.convertValue(value, String.class)); - } catch (RuntimeException e) { - } - try { - return of(ObjectMappers.JSON_MAPPER.convertValue(value, new TypeReference>() {})); - } catch (RuntimeException e) { - } - throw new JsonParseException(p, "Failed to deserialize"); - } - } -} diff --git a/src/main/java/com/deepgram/types/ListenV2ProfanityFilter.java b/src/main/java/com/deepgram/types/ListenV2ProfanityFilter.java new file mode 100644 index 0000000..dff0d21 --- /dev/null +++ b/src/main/java/com/deepgram/types/ListenV2ProfanityFilter.java @@ -0,0 +1,84 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class ListenV2ProfanityFilter { + public static final ListenV2ProfanityFilter FALSE = new ListenV2ProfanityFilter(Value.FALSE, "false"); + + public static final ListenV2ProfanityFilter TRUE = new ListenV2ProfanityFilter(Value.TRUE, "true"); + + private final Value value; + + private final String string; + + ListenV2ProfanityFilter(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 ListenV2ProfanityFilter + && this.string.equals(((ListenV2ProfanityFilter) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case FALSE: + return visitor.visitFalse(); + case TRUE: + return visitor.visitTrue(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static ListenV2ProfanityFilter valueOf(String value) { + switch (value) { + case "false": + return FALSE; + case "true": + return TRUE; + default: + return new ListenV2ProfanityFilter(Value.UNKNOWN, value); + } + } + + public enum Value { + TRUE, + + FALSE, + + UNKNOWN + } + + public interface Visitor { + T visitTrue(); + + T visitFalse(); + + T visitUnknown(String unknownType); + } +} From 37c77e1138aeb61898309543052576b82d9e083e Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Mon, 15 Jun 2026 12:57:07 +0100 Subject: [PATCH 4/7] chore: re-apply manual patches after regen - ClientOptions.java: restore deepgram-java-sdk header constants + x-release-please-version markers (gen reverted to wrong SDK name and hardcoded 0.5.1) - ReconnectingWebSocketListener.java: re-apply maxRetries(0) semantics, configurable connectionTimeoutMs, and applyOptionsOverride() hook - .fernignore: restore both original paths (still frozen for next cycle) - examples/listen/LiveStreamingV2.java: drop deleted ListenV2CloseStreamType; ListenV2CloseStream no longer takes a type - remove transient .bak files --- .fernignore | 4 +- examples/listen/LiveStreamingV2.java | 5 +- .../java/com/deepgram/core/ClientOptions.java | 6 +- .../com/deepgram/core/ClientOptions.java.bak | 241 -------- .../core/ReconnectingWebSocketListener.java | 74 ++- .../ReconnectingWebSocketListener.java.bak | 550 ------------------ 6 files changed, 71 insertions(+), 809 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 diff --git a/.fernignore b/.fernignore index 7b48e95..a14a77f 100644 --- a/.fernignore +++ b/.fernignore @@ -14,7 +14,7 @@ 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. -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/ @@ -22,7 +22,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 # Build and project configuration build.gradle diff --git a/examples/listen/LiveStreamingV2.java b/examples/listen/LiveStreamingV2.java index 5d9d061..cfb0579 100644 --- a/examples/listen/LiveStreamingV2.java +++ b/examples/listen/LiveStreamingV2.java @@ -1,6 +1,5 @@ import com.deepgram.DeepgramClient; import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; -import com.deepgram.resources.listen.v2.types.ListenV2CloseStreamType; import com.deepgram.resources.listen.v2.types.ListenV2TurnInfoEvent; import com.deepgram.resources.listen.v2.websocket.V2ConnectOptions; import com.deepgram.resources.listen.v2.websocket.V2WebSocketClient; @@ -78,9 +77,7 @@ public static void main(String[] args) { // wsClient.sendMedia(audioChunk); // Close the stream - wsClient.sendCloseStream(ListenV2CloseStream.builder() - .type(ListenV2CloseStreamType.CLOSE_STREAM) - .build()); + wsClient.sendCloseStream(ListenV2CloseStream.builder().build()); // Wait for disconnection closeLatch.await(15, TimeUnit.SECONDS); diff --git a/src/main/java/com/deepgram/core/ClientOptions.java b/src/main/java/com/deepgram/core/ClientOptions.java index 1efe669..1cf3a62 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -41,10 +41,10 @@ private ClientOptions( this.headers.putAll(headers); this.headers.putAll(new HashMap() { { - put("User-Agent", "com.deepgram:deepgram-sdk/0.5.1"); + put("User-Agent", "com.deepgram:deepgram-java-sdk/0.5.0"); // x-release-please-version put("X-Fern-Language", "JAVA"); - put("X-Fern-SDK-Name", "com.deepgram.fern:api-sdk"); - put("X-Fern-SDK-Version", "0.5.1"); + put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); + put("X-Fern-SDK-Version", "0.5.0"); // x-release-please-version } }); this.headerSuppliers = headerSuppliers; 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 1cf3a62..0000000 --- a/src/main/java/com/deepgram/core/ClientOptions.java.bak +++ /dev/null @@ -1,241 +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 int timeout; - - private final int maxRetries; - - private final Optional webSocketFactory; - - private final Optional logging; - - private ClientOptions( - Environment environment, - Map headers, - Map> headerSuppliers, - OkHttpClient httpClient, - int timeout, - int maxRetries, - 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.5.0"); // 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.5.0"); // x-release-please-version - } - }); - this.headerSuppliers = headerSuppliers; - this.httpClient = httpClient; - this.timeout = timeout; - this.maxRetries = maxRetries; - 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 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 timeout = Optional.empty(); - - private OkHttpClient httpClient = null; - - 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) { - 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; - } - - public Builder httpClient(OkHttpClient httpClient) { - this.httpClient = httpClient; - 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)); - } - - Logger logger = Logger.from(this.logging); - httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); - - this.httpClient = httpClientBuilder.build(); - this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); - - return new ClientOptions( - environment, - headers, - headerSuppliers, - httpClient, - this.timeout.get(), - this.maxRetries, - 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.headers.putAll(clientOptions.headers); - builder.headerSuppliers.putAll(clientOptions.headerSuppliers); - builder.maxRetries = clientOptions.maxRetries(); - 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 0ca455a..e10e5af 100644 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java @@ -25,16 +25,21 @@ * Provides production-ready resilience for WebSocket connections. */ public abstract class ReconnectingWebSocketListener extends WebSocketListener { - private final long minReconnectionDelayMs; + // Option-derived fields are volatile (not final) so {@link #applyOptionsOverride} can rewire them + // after construction — used by {@code TransportWebSocketFactory} to honour + // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. + private volatile long minReconnectionDelayMs; - private final long maxReconnectionDelayMs; + private volatile long maxReconnectionDelayMs; - private final double reconnectionDelayGrowFactor; + private volatile double reconnectionDelayGrowFactor; - private final int maxRetries; + private volatile int maxRetries; private final int maxEnqueuedMessages; + private volatile long connectionTimeoutMs; + private final AtomicInteger retryCount = new AtomicInteger(0); private final AtomicBoolean connectLock = new AtomicBoolean(false); @@ -66,16 +71,44 @@ public ReconnectingWebSocketListener( this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; this.maxRetries = options.maxRetries; this.maxEnqueuedMessages = options.maxEnqueuedMessages; + this.connectionTimeoutMs = options.connectionTimeoutMs; this.connectionSupplier = connectionSupplier; } + /** + * Replaces the option-derived parameters on this listener at runtime. Used by + * {@code TransportWebSocketFactory} to apply {@code DeepgramTransportFactory.reconnectOptions()} + * without requiring edits to the generated per-resource WebSocket clients. {@code maxEnqueuedMessages} + * is intentionally not overridden — the message queue is sized at construction. + * + *

Thread-safety: option-derived fields are volatile; reads observe the latest write. The + * initial connect() call may have already started before the override lands, so for the very + * first attempt the original options apply; the override takes effect from the next attempt + * onwards. For the SageMaker storm-suppression case ({@code maxRetries(0)}) this is fine + * because the initial attempt's gate ({@code retryCount > maxRetries} with {@code retryCount=0}) + * always passes regardless. + * + * @param options replacement options; {@code null} is a no-op. + */ + public void applyOptionsOverride(ReconnectOptions options) { + if (options == null) { + return; + } + this.minReconnectionDelayMs = options.minReconnectionDelayMs; + this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; + this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; + this.maxRetries = options.maxRetries; + this.connectionTimeoutMs = options.connectionTimeoutMs; + } + /** * 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 — the initial attempt always proceeds. + * {@code maxRetries(0)} means "connect once, don't retry" (not "refuse to connect"). * * Error handling: * - TimeoutException: Includes retry attempt context @@ -86,18 +119,21 @@ public void connect() { if (!connectLock.compareAndSet(false, true)) { return; } - if (retryCount.get() >= maxRetries) { + // retryCount is incremented inside scheduleReconnect() before re-entering connect(), + // so on the initial call retryCount == 0 and we always proceed. The cap applies to + // retries only — maxRetries(0) blocks retries but allows the initial attempt. + if (retryCount.get() > maxRetries) { connectLock.set(false); return; } try { CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); try { - webSocket = connectionFuture.get(4000, MILLISECONDS); + webSocket = connectionFuture.get(connectionTimeoutMs, MILLISECONDS); } catch (TimeoutException e) { connectionFuture.cancel(true); TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + 4000 + " milliseconds" + new TimeoutException("WebSocket connection timeout after " + connectionTimeoutMs + " milliseconds" + (retryCount.get() > 0 ? " (retry attempt #" + retryCount.get() : " (initial connection attempt)")); @@ -399,12 +435,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() { @@ -422,12 +461,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) { @@ -455,6 +497,16 @@ public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { return this; } + /** + * Sets the per-attempt connection timeout in milliseconds. Defaults to {@code 4000}. + * Each call to {@link ReconnectingWebSocketListener#connect()} will wait at most + * this long for the underlying WebSocket factory to produce a connected socket. + */ + public Builder connectionTimeoutMs(long connectionTimeoutMs) { + this.connectionTimeoutMs = connectionTimeoutMs; + return this; + } + /** * Builds the ReconnectOptions with validation. * @@ -463,6 +515,7 @@ public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { * - minReconnectionDelayMs <= maxReconnectionDelayMs * - reconnectionDelayGrowFactor >= 1.0 * - maxRetries and maxEnqueuedMessages are non-negative + * - connectionTimeoutMs is positive * * @return The validated ReconnectOptions instance * @throws IllegalArgumentException if configuration is invalid @@ -487,6 +540,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 e10e5af..0000000 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak +++ /dev/null @@ -1,550 +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.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 { - // Option-derived fields are volatile (not final) so {@link #applyOptionsOverride} can rewire them - // after construction — used by {@code TransportWebSocketFactory} to honour - // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. - private volatile long minReconnectionDelayMs; - - private volatile long maxReconnectionDelayMs; - - private volatile double reconnectionDelayGrowFactor; - - private volatile int maxRetries; - - private final int maxEnqueuedMessages; - - private volatile long connectionTimeoutMs; - - 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.minReconnectionDelayMs = options.minReconnectionDelayMs; - this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; - this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; - this.maxRetries = options.maxRetries; - this.maxEnqueuedMessages = options.maxEnqueuedMessages; - this.connectionTimeoutMs = options.connectionTimeoutMs; - this.connectionSupplier = connectionSupplier; - } - - /** - * Replaces the option-derived parameters on this listener at runtime. Used by - * {@code TransportWebSocketFactory} to apply {@code DeepgramTransportFactory.reconnectOptions()} - * without requiring edits to the generated per-resource WebSocket clients. {@code maxEnqueuedMessages} - * is intentionally not overridden — the message queue is sized at construction. - * - *

Thread-safety: option-derived fields are volatile; reads observe the latest write. The - * initial connect() call may have already started before the override lands, so for the very - * first attempt the original options apply; the override takes effect from the next attempt - * onwards. For the SageMaker storm-suppression case ({@code maxRetries(0)}) this is fine - * because the initial attempt's gate ({@code retryCount > maxRetries} with {@code retryCount=0}) - * always passes regardless. - * - * @param options replacement options; {@code null} is a no-op. - */ - public void applyOptionsOverride(ReconnectOptions options) { - if (options == null) { - return; - } - this.minReconnectionDelayMs = options.minReconnectionDelayMs; - this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; - this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; - this.maxRetries = options.maxRetries; - this.connectionTimeoutMs = options.connectionTimeoutMs; - } - - /** - * 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 — the initial attempt always proceeds. - * {@code maxRetries(0)} means "connect once, don't retry" (not "refuse to connect"). - * - * 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; - } - // retryCount is incremented inside scheduleReconnect() before re-entering connect(), - // so on the initial call retryCount == 0 and we always proceed. The cap applies to - // retries only — maxRetries(0) blocks retries but allows the initial attempt. - if (retryCount.get() > maxRetries) { - connectLock.set(false); - return; - } - try { - CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); - try { - webSocket = connectionFuture.get(connectionTimeoutMs, MILLISECONDS); - } catch (TimeoutException e) { - connectionFuture.cancel(true); - TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + 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) { - WebSocket ws = webSocket; - if (ws != null) { - boolean sent = ws.send(message); - 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) { - WebSocket ws = webSocket; - if (ws != null) { - boolean sent = ws.send(data); - 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(); - } - } - - /** - * @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 (code != 1000 && shouldReconnect.get()) { - scheduleReconnect(); - } - } - - /** - * 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() { - if (retryCount.get() == 1) { - return minReconnectionDelayMs; - } - 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. - */ - 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}. - * Each call to {@link ReconnectingWebSocketListener#connect()} will wait at most - * this long for the underlying WebSocket factory to produce a connected socket. - */ - 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 - * - connectionTimeoutMs is positive - * - * @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); - } - } - } -} From 3b3c6560e90601b3280021fd6446376477ac8d6d Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Tue, 16 Jun 2026 09:27:27 +0100 Subject: [PATCH 5/7] chore: unfreeze files pending regen Re-prepare for a second regeneration on this branch (no new branch/PR). --- .fernignore | 4 +- .../com/deepgram/core/ClientOptions.java.bak | 241 ++++++++ .../ReconnectingWebSocketListener.java.bak | 550 ++++++++++++++++++ 3 files changed, 793 insertions(+), 2 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 diff --git a/.fernignore b/.fernignore index a14a77f..7b48e95 100644 --- a/.fernignore +++ b/.fernignore @@ -14,7 +14,7 @@ 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. -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/ @@ -22,7 +22,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 # 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..1cf3a62 --- /dev/null +++ b/src/main/java/com/deepgram/core/ClientOptions.java.bak @@ -0,0 +1,241 @@ +/** + * 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 int timeout; + + private final int maxRetries; + + private final Optional webSocketFactory; + + private final Optional logging; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout, + int maxRetries, + 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.5.0"); // 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.5.0"); // x-release-please-version + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + this.maxRetries = maxRetries; + 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 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 timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + 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) { + 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; + } + + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + 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)); + } + + Logger logger = Logger.from(this.logging); + httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions( + environment, + headers, + headerSuppliers, + httpClient, + this.timeout.get(), + this.maxRetries, + 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.headers.putAll(clientOptions.headers); + builder.headerSuppliers.putAll(clientOptions.headerSuppliers); + builder.maxRetries = clientOptions.maxRetries(); + 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..e10e5af --- /dev/null +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak @@ -0,0 +1,550 @@ +/** + * 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.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 { + // Option-derived fields are volatile (not final) so {@link #applyOptionsOverride} can rewire them + // after construction — used by {@code TransportWebSocketFactory} to honour + // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. + private volatile long minReconnectionDelayMs; + + private volatile long maxReconnectionDelayMs; + + private volatile double reconnectionDelayGrowFactor; + + private volatile int maxRetries; + + private final int maxEnqueuedMessages; + + private volatile long connectionTimeoutMs; + + 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.minReconnectionDelayMs = options.minReconnectionDelayMs; + this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; + this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; + this.maxRetries = options.maxRetries; + this.maxEnqueuedMessages = options.maxEnqueuedMessages; + this.connectionTimeoutMs = options.connectionTimeoutMs; + this.connectionSupplier = connectionSupplier; + } + + /** + * Replaces the option-derived parameters on this listener at runtime. Used by + * {@code TransportWebSocketFactory} to apply {@code DeepgramTransportFactory.reconnectOptions()} + * without requiring edits to the generated per-resource WebSocket clients. {@code maxEnqueuedMessages} + * is intentionally not overridden — the message queue is sized at construction. + * + *

Thread-safety: option-derived fields are volatile; reads observe the latest write. The + * initial connect() call may have already started before the override lands, so for the very + * first attempt the original options apply; the override takes effect from the next attempt + * onwards. For the SageMaker storm-suppression case ({@code maxRetries(0)}) this is fine + * because the initial attempt's gate ({@code retryCount > maxRetries} with {@code retryCount=0}) + * always passes regardless. + * + * @param options replacement options; {@code null} is a no-op. + */ + public void applyOptionsOverride(ReconnectOptions options) { + if (options == null) { + return; + } + this.minReconnectionDelayMs = options.minReconnectionDelayMs; + this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; + this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; + this.maxRetries = options.maxRetries; + this.connectionTimeoutMs = options.connectionTimeoutMs; + } + + /** + * 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 — the initial attempt always proceeds. + * {@code maxRetries(0)} means "connect once, don't retry" (not "refuse to connect"). + * + * 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; + } + // retryCount is incremented inside scheduleReconnect() before re-entering connect(), + // so on the initial call retryCount == 0 and we always proceed. The cap applies to + // retries only — maxRetries(0) blocks retries but allows the initial attempt. + if (retryCount.get() > maxRetries) { + connectLock.set(false); + return; + } + try { + CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); + try { + webSocket = connectionFuture.get(connectionTimeoutMs, MILLISECONDS); + } catch (TimeoutException e) { + connectionFuture.cancel(true); + TimeoutException timeoutError = + new TimeoutException("WebSocket connection timeout after " + 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) { + WebSocket ws = webSocket; + if (ws != null) { + boolean sent = ws.send(message); + 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) { + WebSocket ws = webSocket; + if (ws != null) { + boolean sent = ws.send(data); + 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(); + } + } + + /** + * @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 (code != 1000 && shouldReconnect.get()) { + scheduleReconnect(); + } + } + + /** + * 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() { + if (retryCount.get() == 1) { + return minReconnectionDelayMs; + } + 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. + */ + 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}. + * Each call to {@link ReconnectingWebSocketListener#connect()} will wait at most + * this long for the underlying WebSocket factory to produce a connected socket. + */ + 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 + * - connectionTimeoutMs is positive + * + * @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); + } + } + } +} From de1675b125a40e05ad5f832df06de3d63d121f08 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:30:50 +0000 Subject: [PATCH 6/7] SDK regeneration --- .fern/metadata.json | 2 +- .../java/com/deepgram/core/ClientOptions.java | 6 +- .../core/ReconnectingWebSocketListener.java | 74 ++------------- .../v2/types/ListenV2TurnInfoWordsItem.java | 89 +++++++------------ 4 files changed, 45 insertions(+), 126 deletions(-) diff --git a/.fern/metadata.json b/.fern/metadata.json index 23e6a0e..f0b05d0 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -11,7 +11,7 @@ }, "enable-wire-tests": true }, - "originGitCommit": "0be656a350cef88f6586d6cb4aa28f8fc25b30ea", + "originGitCommit": "a2c7ddbf260366af00a0e541a7e9ad4de19acabe", "originGitCommitIsDirty": true, "invokedBy": "manual", "sdkVersion": "0.5.1" diff --git a/src/main/java/com/deepgram/core/ClientOptions.java b/src/main/java/com/deepgram/core/ClientOptions.java index 1cf3a62..1efe669 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -41,10 +41,10 @@ private ClientOptions( this.headers.putAll(headers); this.headers.putAll(new HashMap() { { - put("User-Agent", "com.deepgram:deepgram-java-sdk/0.5.0"); // x-release-please-version + put("User-Agent", "com.deepgram:deepgram-sdk/0.5.1"); put("X-Fern-Language", "JAVA"); - put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); - put("X-Fern-SDK-Version", "0.5.0"); // x-release-please-version + put("X-Fern-SDK-Name", "com.deepgram.fern:api-sdk"); + put("X-Fern-SDK-Version", "0.5.1"); } }); this.headerSuppliers = headerSuppliers; diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java index e10e5af..0ca455a 100644 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java @@ -25,21 +25,16 @@ * Provides production-ready resilience for WebSocket connections. */ public abstract class ReconnectingWebSocketListener extends WebSocketListener { - // Option-derived fields are volatile (not final) so {@link #applyOptionsOverride} can rewire them - // after construction — used by {@code TransportWebSocketFactory} to honour - // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. - private volatile long minReconnectionDelayMs; + private final long minReconnectionDelayMs; - private volatile long maxReconnectionDelayMs; + private final long maxReconnectionDelayMs; - private volatile double reconnectionDelayGrowFactor; + private final double reconnectionDelayGrowFactor; - private volatile int maxRetries; + private final int maxRetries; private final int maxEnqueuedMessages; - private volatile long connectionTimeoutMs; - private final AtomicInteger retryCount = new AtomicInteger(0); private final AtomicBoolean connectLock = new AtomicBoolean(false); @@ -71,44 +66,16 @@ public ReconnectingWebSocketListener( this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; this.maxRetries = options.maxRetries; this.maxEnqueuedMessages = options.maxEnqueuedMessages; - this.connectionTimeoutMs = options.connectionTimeoutMs; this.connectionSupplier = connectionSupplier; } - /** - * Replaces the option-derived parameters on this listener at runtime. Used by - * {@code TransportWebSocketFactory} to apply {@code DeepgramTransportFactory.reconnectOptions()} - * without requiring edits to the generated per-resource WebSocket clients. {@code maxEnqueuedMessages} - * is intentionally not overridden — the message queue is sized at construction. - * - *

Thread-safety: option-derived fields are volatile; reads observe the latest write. The - * initial connect() call may have already started before the override lands, so for the very - * first attempt the original options apply; the override takes effect from the next attempt - * onwards. For the SageMaker storm-suppression case ({@code maxRetries(0)}) this is fine - * because the initial attempt's gate ({@code retryCount > maxRetries} with {@code retryCount=0}) - * always passes regardless. - * - * @param options replacement options; {@code null} is a no-op. - */ - public void applyOptionsOverride(ReconnectOptions options) { - if (options == null) { - return; - } - this.minReconnectionDelayMs = options.minReconnectionDelayMs; - this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; - this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; - this.maxRetries = options.maxRetries; - this.connectionTimeoutMs = options.connectionTimeoutMs; - } - /** * 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 — the initial attempt always proceeds. - * {@code maxRetries(0)} means "connect once, don't retry" (not "refuse to connect"). + * - Retry count not incremented for initial connection attempt * * Error handling: * - TimeoutException: Includes retry attempt context @@ -119,21 +86,18 @@ public void connect() { if (!connectLock.compareAndSet(false, true)) { return; } - // retryCount is incremented inside scheduleReconnect() before re-entering connect(), - // so on the initial call retryCount == 0 and we always proceed. The cap applies to - // retries only — maxRetries(0) blocks retries but allows the initial attempt. - if (retryCount.get() > maxRetries) { + if (retryCount.get() >= maxRetries) { connectLock.set(false); return; } try { CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); try { - webSocket = connectionFuture.get(connectionTimeoutMs, MILLISECONDS); + webSocket = connectionFuture.get(4000, MILLISECONDS); } catch (TimeoutException e) { connectionFuture.cancel(true); TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + connectionTimeoutMs + " milliseconds" + new TimeoutException("WebSocket connection timeout after " + 4000 + " milliseconds" + (retryCount.get() > 0 ? " (retry attempt #" + retryCount.get() : " (initial connection attempt)")); @@ -435,15 +399,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() { @@ -461,15 +422,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) { @@ -497,16 +455,6 @@ public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { return this; } - /** - * Sets the per-attempt connection timeout in milliseconds. Defaults to {@code 4000}. - * Each call to {@link ReconnectingWebSocketListener#connect()} will wait at most - * this long for the underlying WebSocket factory to produce a connected socket. - */ - public Builder connectionTimeoutMs(long connectionTimeoutMs) { - this.connectionTimeoutMs = connectionTimeoutMs; - return this; - } - /** * Builds the ReconnectOptions with validation. * @@ -515,7 +463,6 @@ public Builder connectionTimeoutMs(long connectionTimeoutMs) { * - minReconnectionDelayMs <= maxReconnectionDelayMs * - reconnectionDelayGrowFactor >= 1.0 * - maxRetries and maxEnqueuedMessages are non-negative - * - connectionTimeoutMs is positive * * @return The validated ReconnectOptions instance * @throws IllegalArgumentException if configuration is invalid @@ -540,9 +487,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/resources/listen/v2/types/ListenV2TurnInfoWordsItem.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfoWordsItem.java index 62a607b..2f8dcd1 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfoWordsItem.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfoWordsItem.java @@ -10,12 +10,10 @@ 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) @@ -25,18 +23,14 @@ public final class ListenV2TurnInfoWordsItem { private final float confidence; - private final Optional start; + private final double start; - private final Optional end; + private final double end; private final Map additionalProperties; private ListenV2TurnInfoWordsItem( - String word, - float confidence, - Optional start, - Optional end, - Map additionalProperties) { + String word, float confidence, double start, double end, Map additionalProperties) { this.word = word; this.confidence = confidence; this.start = start; @@ -64,7 +58,7 @@ public float getConfidence() { * @return The start time of the word */ @JsonProperty("start") - public Optional getStart() { + public double getStart() { return start; } @@ -72,7 +66,7 @@ public Optional getStart() { * @return The end time of the word */ @JsonProperty("end") - public Optional getEnd() { + public double getEnd() { return end; } @@ -88,10 +82,7 @@ public Map getAdditionalProperties() { } private boolean equalTo(ListenV2TurnInfoWordsItem other) { - return word.equals(other.word) - && confidence == other.confidence - && start.equals(other.start) - && end.equals(other.end); + return word.equals(other.word) && confidence == other.confidence && start == other.start && end == other.end; } @java.lang.Override @@ -121,40 +112,40 @@ public interface ConfidenceStage { /** *

Confidence that this word was transcribed correctly

*/ - _FinalStage confidence(float confidence); + StartStage confidence(float confidence); } - public interface _FinalStage { - ListenV2TurnInfoWordsItem build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - + public interface StartStage { /** *

The start time of the word

*/ - _FinalStage start(Optional start); - - _FinalStage start(Float start); + EndStage start(double start); + } + public interface EndStage { /** *

The end time of the word

*/ - _FinalStage end(Optional end); + _FinalStage end(double end); + } - _FinalStage end(Float end); + public interface _FinalStage { + ListenV2TurnInfoWordsItem build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); } @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements WordStage, ConfidenceStage, _FinalStage { + public static final class Builder implements WordStage, ConfidenceStage, StartStage, EndStage, _FinalStage { private String word; private float confidence; - private Optional end = Optional.empty(); + private double start; - private Optional start = Optional.empty(); + private double end; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -189,48 +180,32 @@ public ConfidenceStage word(@NotNull String word) { */ @java.lang.Override @JsonSetter("confidence") - public _FinalStage confidence(float confidence) { + public StartStage confidence(float confidence) { this.confidence = confidence; return this; } /** - *

The end time of the word

+ *

The start time of the word

+ *

The start time of the word

* @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override - public _FinalStage end(Float end) { - this.end = Optional.ofNullable(end); + @JsonSetter("start") + public EndStage start(double start) { + this.start = start; return this; } /** *

The end time of the word

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

The start time of the word

+ *

The end time of the word

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

The start time of the word

- */ - @java.lang.Override - @JsonSetter(value = "start", nulls = Nulls.SKIP) - public _FinalStage start(Optional start) { - this.start = start; + @JsonSetter("end") + public _FinalStage end(double end) { + this.end = end; return this; } From e19ec825e380cf490f8fbc982230f875cc3dda94 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Tue, 16 Jun 2026 09:38:43 +0100 Subject: [PATCH 7/7] chore: re-apply manual patches after regen Second regeneration on this branch. Notable generator change: ListenV2TurnInfoWordsItem start/end are now required double (was Optional). - ClientOptions.java: restore deepgram-java-sdk header constants + x-release-please-version markers (gen reverted to wrong SDK name / 0.5.1) - ReconnectingWebSocketListener.java: re-apply maxRetries(0) semantics, configurable connectionTimeoutMs, applyOptionsOverride() hook - .fernignore: restore both original paths - remove transient .bak files --- .fernignore | 4 +- .../java/com/deepgram/core/ClientOptions.java | 6 +- .../com/deepgram/core/ClientOptions.java.bak | 241 -------- .../core/ReconnectingWebSocketListener.java | 74 ++- .../ReconnectingWebSocketListener.java.bak | 550 ------------------ 5 files changed, 70 insertions(+), 805 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 diff --git a/.fernignore b/.fernignore index 7b48e95..a14a77f 100644 --- a/.fernignore +++ b/.fernignore @@ -14,7 +14,7 @@ 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. -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/ @@ -22,7 +22,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 # 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 1efe669..1cf3a62 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -41,10 +41,10 @@ private ClientOptions( this.headers.putAll(headers); this.headers.putAll(new HashMap() { { - put("User-Agent", "com.deepgram:deepgram-sdk/0.5.1"); + put("User-Agent", "com.deepgram:deepgram-java-sdk/0.5.0"); // x-release-please-version put("X-Fern-Language", "JAVA"); - put("X-Fern-SDK-Name", "com.deepgram.fern:api-sdk"); - put("X-Fern-SDK-Version", "0.5.1"); + put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); + put("X-Fern-SDK-Version", "0.5.0"); // x-release-please-version } }); this.headerSuppliers = headerSuppliers; 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 1cf3a62..0000000 --- a/src/main/java/com/deepgram/core/ClientOptions.java.bak +++ /dev/null @@ -1,241 +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 int timeout; - - private final int maxRetries; - - private final Optional webSocketFactory; - - private final Optional logging; - - private ClientOptions( - Environment environment, - Map headers, - Map> headerSuppliers, - OkHttpClient httpClient, - int timeout, - int maxRetries, - 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.5.0"); // 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.5.0"); // x-release-please-version - } - }); - this.headerSuppliers = headerSuppliers; - this.httpClient = httpClient; - this.timeout = timeout; - this.maxRetries = maxRetries; - 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 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 timeout = Optional.empty(); - - private OkHttpClient httpClient = null; - - 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) { - 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; - } - - public Builder httpClient(OkHttpClient httpClient) { - this.httpClient = httpClient; - 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)); - } - - Logger logger = Logger.from(this.logging); - httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); - - this.httpClient = httpClientBuilder.build(); - this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); - - return new ClientOptions( - environment, - headers, - headerSuppliers, - httpClient, - this.timeout.get(), - this.maxRetries, - 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.headers.putAll(clientOptions.headers); - builder.headerSuppliers.putAll(clientOptions.headerSuppliers); - builder.maxRetries = clientOptions.maxRetries(); - 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 0ca455a..e10e5af 100644 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java @@ -25,16 +25,21 @@ * Provides production-ready resilience for WebSocket connections. */ public abstract class ReconnectingWebSocketListener extends WebSocketListener { - private final long minReconnectionDelayMs; + // Option-derived fields are volatile (not final) so {@link #applyOptionsOverride} can rewire them + // after construction — used by {@code TransportWebSocketFactory} to honour + // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. + private volatile long minReconnectionDelayMs; - private final long maxReconnectionDelayMs; + private volatile long maxReconnectionDelayMs; - private final double reconnectionDelayGrowFactor; + private volatile double reconnectionDelayGrowFactor; - private final int maxRetries; + private volatile int maxRetries; private final int maxEnqueuedMessages; + private volatile long connectionTimeoutMs; + private final AtomicInteger retryCount = new AtomicInteger(0); private final AtomicBoolean connectLock = new AtomicBoolean(false); @@ -66,16 +71,44 @@ public ReconnectingWebSocketListener( this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; this.maxRetries = options.maxRetries; this.maxEnqueuedMessages = options.maxEnqueuedMessages; + this.connectionTimeoutMs = options.connectionTimeoutMs; this.connectionSupplier = connectionSupplier; } + /** + * Replaces the option-derived parameters on this listener at runtime. Used by + * {@code TransportWebSocketFactory} to apply {@code DeepgramTransportFactory.reconnectOptions()} + * without requiring edits to the generated per-resource WebSocket clients. {@code maxEnqueuedMessages} + * is intentionally not overridden — the message queue is sized at construction. + * + *

Thread-safety: option-derived fields are volatile; reads observe the latest write. The + * initial connect() call may have already started before the override lands, so for the very + * first attempt the original options apply; the override takes effect from the next attempt + * onwards. For the SageMaker storm-suppression case ({@code maxRetries(0)}) this is fine + * because the initial attempt's gate ({@code retryCount > maxRetries} with {@code retryCount=0}) + * always passes regardless. + * + * @param options replacement options; {@code null} is a no-op. + */ + public void applyOptionsOverride(ReconnectOptions options) { + if (options == null) { + return; + } + this.minReconnectionDelayMs = options.minReconnectionDelayMs; + this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; + this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; + this.maxRetries = options.maxRetries; + this.connectionTimeoutMs = options.connectionTimeoutMs; + } + /** * 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 — the initial attempt always proceeds. + * {@code maxRetries(0)} means "connect once, don't retry" (not "refuse to connect"). * * Error handling: * - TimeoutException: Includes retry attempt context @@ -86,18 +119,21 @@ public void connect() { if (!connectLock.compareAndSet(false, true)) { return; } - if (retryCount.get() >= maxRetries) { + // retryCount is incremented inside scheduleReconnect() before re-entering connect(), + // so on the initial call retryCount == 0 and we always proceed. The cap applies to + // retries only — maxRetries(0) blocks retries but allows the initial attempt. + if (retryCount.get() > maxRetries) { connectLock.set(false); return; } try { CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); try { - webSocket = connectionFuture.get(4000, MILLISECONDS); + webSocket = connectionFuture.get(connectionTimeoutMs, MILLISECONDS); } catch (TimeoutException e) { connectionFuture.cancel(true); TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + 4000 + " milliseconds" + new TimeoutException("WebSocket connection timeout after " + connectionTimeoutMs + " milliseconds" + (retryCount.get() > 0 ? " (retry attempt #" + retryCount.get() : " (initial connection attempt)")); @@ -399,12 +435,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() { @@ -422,12 +461,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) { @@ -455,6 +497,16 @@ public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { return this; } + /** + * Sets the per-attempt connection timeout in milliseconds. Defaults to {@code 4000}. + * Each call to {@link ReconnectingWebSocketListener#connect()} will wait at most + * this long for the underlying WebSocket factory to produce a connected socket. + */ + public Builder connectionTimeoutMs(long connectionTimeoutMs) { + this.connectionTimeoutMs = connectionTimeoutMs; + return this; + } + /** * Builds the ReconnectOptions with validation. * @@ -463,6 +515,7 @@ public Builder maxEnqueuedMessages(int maxEnqueuedMessages) { * - minReconnectionDelayMs <= maxReconnectionDelayMs * - reconnectionDelayGrowFactor >= 1.0 * - maxRetries and maxEnqueuedMessages are non-negative + * - connectionTimeoutMs is positive * * @return The validated ReconnectOptions instance * @throws IllegalArgumentException if configuration is invalid @@ -487,6 +540,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 e10e5af..0000000 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak +++ /dev/null @@ -1,550 +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.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 { - // Option-derived fields are volatile (not final) so {@link #applyOptionsOverride} can rewire them - // after construction — used by {@code TransportWebSocketFactory} to honour - // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. - private volatile long minReconnectionDelayMs; - - private volatile long maxReconnectionDelayMs; - - private volatile double reconnectionDelayGrowFactor; - - private volatile int maxRetries; - - private final int maxEnqueuedMessages; - - private volatile long connectionTimeoutMs; - - 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.minReconnectionDelayMs = options.minReconnectionDelayMs; - this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; - this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; - this.maxRetries = options.maxRetries; - this.maxEnqueuedMessages = options.maxEnqueuedMessages; - this.connectionTimeoutMs = options.connectionTimeoutMs; - this.connectionSupplier = connectionSupplier; - } - - /** - * Replaces the option-derived parameters on this listener at runtime. Used by - * {@code TransportWebSocketFactory} to apply {@code DeepgramTransportFactory.reconnectOptions()} - * without requiring edits to the generated per-resource WebSocket clients. {@code maxEnqueuedMessages} - * is intentionally not overridden — the message queue is sized at construction. - * - *

Thread-safety: option-derived fields are volatile; reads observe the latest write. The - * initial connect() call may have already started before the override lands, so for the very - * first attempt the original options apply; the override takes effect from the next attempt - * onwards. For the SageMaker storm-suppression case ({@code maxRetries(0)}) this is fine - * because the initial attempt's gate ({@code retryCount > maxRetries} with {@code retryCount=0}) - * always passes regardless. - * - * @param options replacement options; {@code null} is a no-op. - */ - public void applyOptionsOverride(ReconnectOptions options) { - if (options == null) { - return; - } - this.minReconnectionDelayMs = options.minReconnectionDelayMs; - this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; - this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; - this.maxRetries = options.maxRetries; - this.connectionTimeoutMs = options.connectionTimeoutMs; - } - - /** - * 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 — the initial attempt always proceeds. - * {@code maxRetries(0)} means "connect once, don't retry" (not "refuse to connect"). - * - * 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; - } - // retryCount is incremented inside scheduleReconnect() before re-entering connect(), - // so on the initial call retryCount == 0 and we always proceed. The cap applies to - // retries only — maxRetries(0) blocks retries but allows the initial attempt. - if (retryCount.get() > maxRetries) { - connectLock.set(false); - return; - } - try { - CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); - try { - webSocket = connectionFuture.get(connectionTimeoutMs, MILLISECONDS); - } catch (TimeoutException e) { - connectionFuture.cancel(true); - TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + 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) { - WebSocket ws = webSocket; - if (ws != null) { - boolean sent = ws.send(message); - 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) { - WebSocket ws = webSocket; - if (ws != null) { - boolean sent = ws.send(data); - 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(); - } - } - - /** - * @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 (code != 1000 && shouldReconnect.get()) { - scheduleReconnect(); - } - } - - /** - * 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() { - if (retryCount.get() == 1) { - return minReconnectionDelayMs; - } - 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. - */ - 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}. - * Each call to {@link ReconnectingWebSocketListener#connect()} will wait at most - * this long for the underlying WebSocket factory to produce a connected socket. - */ - 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 - * - connectionTimeoutMs is positive - * - * @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); - } - } - } -}