From 1ab501275d3a390e9359783f099e11a7bc5762dd Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Thu, 14 May 2026 12:54:47 +0100 Subject: [PATCH 1/4] chore: initialize SDK regeneration branch From 1ad406a6c8788aa116ec124e3a7473dd0a98bd69 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Thu, 14 May 2026 12:56:20 +0100 Subject: [PATCH 2/4] 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..89993e2 --- /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.4.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.4.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 e0ab5198b37a2134ff1342f95f9fd7fc6c88ae57 Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 11:59:16 +0000 Subject: [PATCH 3/4] SDK regeneration --- .fern/metadata.json | 4 +- .../java/com/deepgram/core/ClientOptions.java | 6 +- .../java/com/deepgram/core/Environment.java | 34 +-- .../core/ReconnectingWebSocketListener.java | 74 +----- .../think/models/AsyncRawModelsClient.java | 2 +- .../think/models/RawModelsClient.java | 2 +- ...tV1SettingsAgentContextListenProvider.java | 22 +- .../AgentV1SettingsAgentListenProvider.java | 22 +- .../AgentV1SettingsAgentListenProviderV1.java | 214 ------------------ .../AgentV1SettingsAgentListenProviderV2.java | 183 --------------- .../listen/v1/media/AsyncRawMediaClient.java | 8 + .../listen/v1/media/RawMediaClient.java | 8 + .../v1/media/requests/ListenV1RequestUrl.java | 46 ++++ .../MediaTranscribeRequestOctetStream.java | 46 ++++ .../MediaTranscribeRequestDiarizeModel.java | 95 ++++++++ .../DeepgramListenProviderV1.java} | 54 +++-- .../DeepgramListenProviderV2.java} | 89 ++++++-- ...DeepgramListenProviderV2LanguageHint.java} | 30 +-- 18 files changed, 382 insertions(+), 557 deletions(-) delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProviderV1.java delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProviderV2.java create mode 100644 src/main/java/com/deepgram/resources/listen/v1/media/types/MediaTranscribeRequestDiarizeModel.java rename src/main/java/com/deepgram/{resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV1.java => types/DeepgramListenProviderV1.java} (79%) rename src/main/java/com/deepgram/{resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV2.java => types/DeepgramListenProviderV2.java} (69%) rename src/main/java/com/deepgram/{resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV2LanguageHint.java => types/DeepgramListenProviderV2LanguageHint.java} (62%) diff --git a/.fern/metadata.json b/.fern/metadata.json index a700e01..5eded74 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -11,8 +11,8 @@ }, "enable-wire-tests": true }, - "originGitCommit": "d1854cf6d560a0e27c9f46c1d83a6d7d9924f045", + "originGitCommit": "0052a020a7becd03b349857664c9f4a89b6c449a", "originGitCommitIsDirty": true, "invokedBy": "manual", - "sdkVersion": "0.4.0" + "sdkVersion": "0.4.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 89993e2..bb2bcfc 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.4.0"); // x-release-please-version + put("User-Agent", "com.deepgram:deepgram-sdk/0.4.1"); put("X-Fern-Language", "JAVA"); - put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); - put("X-Fern-SDK-Version", "0.4.0"); // x-release-please-version + put("X-Fern-SDK-Name", "com.deepgram.fern:api-sdk"); + put("X-Fern-SDK-Version", "0.4.1"); } }); this.headerSuppliers = headerSuppliers; diff --git a/src/main/java/com/deepgram/core/Environment.java b/src/main/java/com/deepgram/core/Environment.java index 681ef6c..ab2a5b6 100644 --- a/src/main/java/com/deepgram/core/Environment.java +++ b/src/main/java/com/deepgram/core/Environment.java @@ -5,35 +5,35 @@ public final class Environment { public static final Environment PRODUCTION = - new Environment("https://api.deepgram.com", "wss://agent.deepgram.com", "wss://api.deepgram.com"); + new Environment("https://api.deepgram.com", "wss://api.deepgram.com", "wss://agent.deepgram.com"); public static final Environment AGENT = - new Environment("https://agent.deepgram.com", "wss://agent.deepgram.com", "wss://api.deepgram.com"); + new Environment("https://agent.deepgram.com", "wss://api.deepgram.com", "wss://agent.deepgram.com"); private final String base; - private final String agent; - private final String production; - Environment(String base, String agent, String production) { + private final String agent; + + Environment(String base, String production, String agent) { this.base = base; - this.agent = agent; this.production = production; + this.agent = agent; } public String getBaseURL() { return this.base; } - public String getAgentURL() { - return this.agent; - } - public String getProductionURL() { return this.production; } + public String getAgentURL() { + return this.agent; + } + public static Builder custom() { return new Builder(); } @@ -41,27 +41,27 @@ public static Builder custom() { public static class Builder { private String base; - private String agent; - private String production; + private String agent; + public Builder base(String base) { this.base = base; return this; } - public Builder agent(String agent) { - this.agent = agent; + public Builder production(String production) { + this.production = production; return this; } - public Builder production(String production) { - this.production = production; + public Builder agent(String agent) { + this.agent = agent; return this; } public Environment build() { - return new Environment(base, agent, production); + return new Environment(base, production, agent); } } } 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/settings/think/models/AsyncRawModelsClient.java b/src/main/java/com/deepgram/resources/agent/v1/settings/think/models/AsyncRawModelsClient.java index 6b629c5..50769cd 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/settings/think/models/AsyncRawModelsClient.java +++ b/src/main/java/com/deepgram/resources/agent/v1/settings/think/models/AsyncRawModelsClient.java @@ -42,7 +42,7 @@ public CompletableFuture> li * Retrieves the available think models that can be used for AI agent processing */ public CompletableFuture> list(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getBaseURL()) + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getAgentURL()) .newBuilder() .addPathSegments("v1/agent/settings/think/models"); if (requestOptions != null) { diff --git a/src/main/java/com/deepgram/resources/agent/v1/settings/think/models/RawModelsClient.java b/src/main/java/com/deepgram/resources/agent/v1/settings/think/models/RawModelsClient.java index 7795451..f1f069b 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/settings/think/models/RawModelsClient.java +++ b/src/main/java/com/deepgram/resources/agent/v1/settings/think/models/RawModelsClient.java @@ -38,7 +38,7 @@ public DeepgramApiHttpResponse list() { * Retrieves the available think models that can be used for AI agent processing */ public DeepgramApiHttpResponse list(RequestOptions requestOptions) { - HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getBaseURL()) + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getAgentURL()) .newBuilder() .addPathSegments("v1/agent/settings/think/models"); if (requestOptions != null) { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java index 606be37..eea1585 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java @@ -3,6 +3,8 @@ */ package com.deepgram.resources.agent.v1.types; +import com.deepgram.types.DeepgramListenProviderV1; +import com.deepgram.types.DeepgramListenProviderV2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -26,11 +28,11 @@ public T visit(Visitor visitor) { return value.visit(visitor); } - public static AgentV1SettingsAgentContextListenProvider v1(AgentV1SettingsAgentContextListenProviderV1 value) { + public static AgentV1SettingsAgentContextListenProvider v1(DeepgramListenProviderV1 value) { return new AgentV1SettingsAgentContextListenProvider(new V1Value(value)); } - public static AgentV1SettingsAgentContextListenProvider v2(AgentV1SettingsAgentContextListenProviderV2 value) { + public static AgentV1SettingsAgentContextListenProvider v2(DeepgramListenProviderV2 value) { return new AgentV1SettingsAgentContextListenProvider(new V2Value(value)); } @@ -46,14 +48,14 @@ public boolean _isUnknown() { return value instanceof _UnknownValue; } - public Optional getV1() { + public Optional getV1() { if (isV1()) { return Optional.of(((V1Value) value).value); } return Optional.empty(); } - public Optional getV2() { + public Optional getV2() { if (isV2()) { return Optional.of(((V2Value) value).value); } @@ -90,9 +92,9 @@ private Value getValue() { } public interface Visitor { - T visitV1(AgentV1SettingsAgentContextListenProviderV1 v1); + T visitV1(DeepgramListenProviderV1 v1); - T visitV2(AgentV1SettingsAgentContextListenProviderV2 v2); + T visitV2(DeepgramListenProviderV2 v2); T _visitUnknown(Object unknownType); } @@ -109,12 +111,12 @@ private interface Value { private static final class V1Value implements Value { @JsonUnwrapped @JsonIgnoreProperties(value = "version", allowSetters = true) - private AgentV1SettingsAgentContextListenProviderV1 value; + private DeepgramListenProviderV1 value; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) private V1Value() {} - private V1Value(AgentV1SettingsAgentContextListenProviderV1 value) { + private V1Value(DeepgramListenProviderV1 value) { this.value = value; } @@ -149,12 +151,12 @@ public String toString() { private static final class V2Value implements Value { @JsonUnwrapped @JsonIgnoreProperties(value = "version", allowSetters = true) - private AgentV1SettingsAgentContextListenProviderV2 value; + private DeepgramListenProviderV2 value; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) private V2Value() {} - private V2Value(AgentV1SettingsAgentContextListenProviderV2 value) { + private V2Value(DeepgramListenProviderV2 value) { this.value = value; } diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java index dd0810b..4309ec0 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java @@ -3,6 +3,8 @@ */ package com.deepgram.resources.agent.v1.types; +import com.deepgram.types.DeepgramListenProviderV1; +import com.deepgram.types.DeepgramListenProviderV2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -26,11 +28,11 @@ public T visit(Visitor visitor) { return value.visit(visitor); } - public static AgentV1SettingsAgentListenProvider v1(AgentV1SettingsAgentListenProviderV1 value) { + public static AgentV1SettingsAgentListenProvider v1(DeepgramListenProviderV1 value) { return new AgentV1SettingsAgentListenProvider(new V1Value(value)); } - public static AgentV1SettingsAgentListenProvider v2(AgentV1SettingsAgentListenProviderV2 value) { + public static AgentV1SettingsAgentListenProvider v2(DeepgramListenProviderV2 value) { return new AgentV1SettingsAgentListenProvider(new V2Value(value)); } @@ -46,14 +48,14 @@ public boolean _isUnknown() { return value instanceof _UnknownValue; } - public Optional getV1() { + public Optional getV1() { if (isV1()) { return Optional.of(((V1Value) value).value); } return Optional.empty(); } - public Optional getV2() { + public Optional getV2() { if (isV2()) { return Optional.of(((V2Value) value).value); } @@ -90,9 +92,9 @@ private Value getValue() { } public interface Visitor { - T visitV1(AgentV1SettingsAgentListenProviderV1 v1); + T visitV1(DeepgramListenProviderV1 v1); - T visitV2(AgentV1SettingsAgentListenProviderV2 v2); + T visitV2(DeepgramListenProviderV2 v2); T _visitUnknown(Object unknownType); } @@ -109,12 +111,12 @@ private interface Value { private static final class V1Value implements Value { @JsonUnwrapped @JsonIgnoreProperties(value = "version", allowSetters = true) - private AgentV1SettingsAgentListenProviderV1 value; + private DeepgramListenProviderV1 value; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) private V1Value() {} - private V1Value(AgentV1SettingsAgentListenProviderV1 value) { + private V1Value(DeepgramListenProviderV1 value) { this.value = value; } @@ -149,12 +151,12 @@ public String toString() { private static final class V2Value implements Value { @JsonUnwrapped @JsonIgnoreProperties(value = "version", allowSetters = true) - private AgentV1SettingsAgentListenProviderV2 value; + private DeepgramListenProviderV2 value; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) private V2Value() {} - private V2Value(AgentV1SettingsAgentListenProviderV2 value) { + private V2Value(DeepgramListenProviderV2 value) { this.value = value; } diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProviderV1.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProviderV1.java deleted file mode 100644 index 4d94cc0..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProviderV1.java +++ /dev/null @@ -1,214 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1SettingsAgentListenProviderV1.Builder.class) -public final class AgentV1SettingsAgentListenProviderV1 { - private final Optional model; - - private final Optional language; - - private final Optional> keyterms; - - private final Optional smartFormat; - - private final Map additionalProperties; - - private AgentV1SettingsAgentListenProviderV1( - Optional model, - Optional language, - Optional> keyterms, - Optional smartFormat, - Map additionalProperties) { - this.model = model; - this.language = language; - this.keyterms = keyterms; - this.smartFormat = smartFormat; - this.additionalProperties = additionalProperties; - } - - /** - * @return Provider type for speech-to-text - */ - @JsonProperty("type") - public String getType() { - return "deepgram"; - } - - /** - * @return Model to use for speech to text using the V1 API (e.g. Nova-3, Nova-2) - */ - @JsonProperty("model") - public Optional getModel() { - return model; - } - - /** - * @return Language code to use for speech-to-text. Can be a BCP-47 language tag (e.g. en), or multi for code-switching transcription - */ - @JsonProperty("language") - public Optional getLanguage() { - return language; - } - - /** - * @return Prompt keyterm recognition to improve Keyword Recall Rate - */ - @JsonProperty("keyterms") - public Optional> getKeyterms() { - return keyterms; - } - - /** - * @return Applies smart formatting to improve transcript readability - */ - @JsonProperty("smart_format") - public Optional getSmartFormat() { - return smartFormat; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SettingsAgentListenProviderV1 - && equalTo((AgentV1SettingsAgentListenProviderV1) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(AgentV1SettingsAgentListenProviderV1 other) { - return model.equals(other.model) - && language.equals(other.language) - && keyterms.equals(other.keyterms) - && smartFormat.equals(other.smartFormat); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.model, this.language, this.keyterms, this.smartFormat); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional model = Optional.empty(); - - private Optional language = Optional.empty(); - - private Optional> keyterms = Optional.empty(); - - private Optional smartFormat = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1SettingsAgentListenProviderV1 other) { - model(other.getModel()); - language(other.getLanguage()); - keyterms(other.getKeyterms()); - smartFormat(other.getSmartFormat()); - return this; - } - - /** - *

Model to use for speech to text using the V1 API (e.g. Nova-3, Nova-2)

- */ - @JsonSetter(value = "model", nulls = Nulls.SKIP) - public Builder model(Optional model) { - this.model = model; - return this; - } - - public Builder model(String model) { - this.model = Optional.ofNullable(model); - return this; - } - - /** - *

Language code to use for speech-to-text. Can be a BCP-47 language tag (e.g. en), or multi for code-switching transcription

- */ - @JsonSetter(value = "language", nulls = Nulls.SKIP) - public Builder language(Optional language) { - this.language = language; - return this; - } - - public Builder language(String language) { - this.language = Optional.ofNullable(language); - return this; - } - - /** - *

Prompt keyterm recognition to improve Keyword Recall Rate

- */ - @JsonSetter(value = "keyterms", nulls = Nulls.SKIP) - public Builder keyterms(Optional> keyterms) { - this.keyterms = keyterms; - return this; - } - - public Builder keyterms(List keyterms) { - this.keyterms = Optional.ofNullable(keyterms); - return this; - } - - /** - *

Applies smart formatting to improve transcript readability

- */ - @JsonSetter(value = "smart_format", nulls = Nulls.SKIP) - public Builder smartFormat(Optional smartFormat) { - this.smartFormat = smartFormat; - return this; - } - - public Builder smartFormat(Boolean smartFormat) { - this.smartFormat = Optional.ofNullable(smartFormat); - return this; - } - - public AgentV1SettingsAgentListenProviderV1 build() { - return new AgentV1SettingsAgentListenProviderV1( - model, language, keyterms, smartFormat, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProviderV2.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProviderV2.java deleted file mode 100644 index 90debd3..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProviderV2.java +++ /dev/null @@ -1,183 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1SettingsAgentListenProviderV2.Builder.class) -public final class AgentV1SettingsAgentListenProviderV2 { - private final String model; - - private final Optional> keyterms; - - private final Map additionalProperties; - - private AgentV1SettingsAgentListenProviderV2( - String model, Optional> keyterms, Map additionalProperties) { - this.model = model; - this.keyterms = keyterms; - this.additionalProperties = additionalProperties; - } - - /** - * @return Provider type for speech-to-text - */ - @JsonProperty("type") - public String getType() { - return "deepgram"; - } - - /** - * @return Model to use for speech to text using the V2 API (e.g. flux-general-en) - */ - @JsonProperty("model") - public String getModel() { - return model; - } - - /** - * @return Prompt keyterm recognition to improve Keyword Recall Rate - */ - @JsonProperty("keyterms") - public Optional> getKeyterms() { - return keyterms; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SettingsAgentListenProviderV2 - && equalTo((AgentV1SettingsAgentListenProviderV2) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(AgentV1SettingsAgentListenProviderV2 other) { - return model.equals(other.model) && keyterms.equals(other.keyterms); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.model, this.keyterms); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static ModelStage builder() { - return new Builder(); - } - - public interface ModelStage { - /** - *

Model to use for speech to text using the V2 API (e.g. flux-general-en)

- */ - _FinalStage model(@NotNull String model); - - Builder from(AgentV1SettingsAgentListenProviderV2 other); - } - - public interface _FinalStage { - AgentV1SettingsAgentListenProviderV2 build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Prompt keyterm recognition to improve Keyword Recall Rate

- */ - _FinalStage keyterms(Optional> keyterms); - - _FinalStage keyterms(List keyterms); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder implements ModelStage, _FinalStage { - private String model; - - private Optional> keyterms = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(AgentV1SettingsAgentListenProviderV2 other) { - model(other.getModel()); - keyterms(other.getKeyterms()); - return this; - } - - /** - *

Model to use for speech to text using the V2 API (e.g. flux-general-en)

- *

Model to use for speech to text using the V2 API (e.g. flux-general-en)

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

Prompt keyterm recognition to improve Keyword Recall Rate

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

Prompt keyterm recognition to improve Keyword Recall Rate

- */ - @java.lang.Override - @JsonSetter(value = "keyterms", nulls = Nulls.SKIP) - public _FinalStage keyterms(Optional> keyterms) { - this.keyterms = keyterms; - return this; - } - - @java.lang.Override - public AgentV1SettingsAgentListenProviderV2 build() { - return new AgentV1SettingsAgentListenProviderV2(model, keyterms, 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/v1/media/AsyncRawMediaClient.java b/src/main/java/com/deepgram/resources/listen/v1/media/AsyncRawMediaClient.java index 5bdd368..a186d87 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/media/AsyncRawMediaClient.java +++ b/src/main/java/com/deepgram/resources/listen/v1/media/AsyncRawMediaClient.java @@ -99,6 +99,10 @@ public CompletableFuture> trans QueryStringMapper.addQueryParameter( httpUrl, "diarize", request.getDiarize().get(), false); } + if (request.getDiarizeModel().isPresent()) { + QueryStringMapper.addQueryParameter( + httpUrl, "diarize_model", request.getDiarizeModel().get(), false); + } if (request.getDictation().isPresent()) { QueryStringMapper.addQueryParameter( httpUrl, "dictation", request.getDictation().get(), false); @@ -336,6 +340,10 @@ public CompletableFuture> trans QueryStringMapper.addQueryParameter( httpUrl, "diarize", request.getDiarize().get(), false); } + if (request.getDiarizeModel().isPresent()) { + QueryStringMapper.addQueryParameter( + httpUrl, "diarize_model", request.getDiarizeModel().get(), false); + } if (request.getDictation().isPresent()) { QueryStringMapper.addQueryParameter( httpUrl, "dictation", request.getDictation().get(), false); diff --git a/src/main/java/com/deepgram/resources/listen/v1/media/RawMediaClient.java b/src/main/java/com/deepgram/resources/listen/v1/media/RawMediaClient.java index 7dbd0fb..f263224 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/media/RawMediaClient.java +++ b/src/main/java/com/deepgram/resources/listen/v1/media/RawMediaClient.java @@ -94,6 +94,10 @@ public DeepgramApiHttpResponse transcribeUrl( QueryStringMapper.addQueryParameter( httpUrl, "diarize", request.getDiarize().get(), false); } + if (request.getDiarizeModel().isPresent()) { + QueryStringMapper.addQueryParameter( + httpUrl, "diarize_model", request.getDiarizeModel().get(), false); + } if (request.getDictation().isPresent()) { QueryStringMapper.addQueryParameter( httpUrl, "dictation", request.getDictation().get(), false); @@ -315,6 +319,10 @@ public DeepgramApiHttpResponse transcribeFile( QueryStringMapper.addQueryParameter( httpUrl, "diarize", request.getDiarize().get(), false); } + if (request.getDiarizeModel().isPresent()) { + QueryStringMapper.addQueryParameter( + httpUrl, "diarize_model", request.getDiarizeModel().get(), false); + } if (request.getDictation().isPresent()) { QueryStringMapper.addQueryParameter( httpUrl, "dictation", request.getDictation().get(), false); 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 52388cf..d59ed17 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 @@ -7,6 +7,7 @@ import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestCallbackMethod; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestCustomIntentMode; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestCustomTopicMode; +import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestDiarizeModel; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestEncoding; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestModel; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestSummarize; @@ -69,6 +70,8 @@ public final class ListenV1RequestUrl { private final Optional diarize; + private final Optional diarizeModel; + private final Optional dictation; private final Optional encoding; @@ -127,6 +130,7 @@ private ListenV1RequestUrl( Optional detectEntities, Optional detectLanguage, Optional diarize, + Optional diarizeModel, Optional dictation, Optional encoding, Optional fillerWords, @@ -165,6 +169,7 @@ private ListenV1RequestUrl( this.detectEntities = detectEntities; this.detectLanguage = detectLanguage; this.diarize = diarize; + this.diarizeModel = diarizeModel; this.dictation = dictation; this.encoding = encoding; this.fillerWords = fillerWords; @@ -338,6 +343,14 @@ 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. + */ + @JsonIgnore + public Optional getDiarizeModel() { + return diarizeModel; + } + /** * @return Dictation mode for controlling formatting with dictated speech */ @@ -510,6 +523,7 @@ private boolean equalTo(ListenV1RequestUrl other) { && detectEntities.equals(other.detectEntities) && detectLanguage.equals(other.detectLanguage) && diarize.equals(other.diarize) + && diarizeModel.equals(other.diarizeModel) && dictation.equals(other.dictation) && encoding.equals(other.encoding) && fillerWords.equals(other.fillerWords) @@ -552,6 +566,7 @@ public int hashCode() { this.detectEntities, this.detectLanguage, this.diarize, + this.diarizeModel, this.dictation, this.encoding, this.fillerWords, @@ -743,6 +758,13 @@ public interface _FinalStage { _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.

+ */ + _FinalStage diarizeModel(Optional diarizeModel); + + _FinalStage diarizeModel(MediaTranscribeRequestDiarizeModel diarizeModel); + /** *

Dictation mode for controlling formatting with dictated speech

*/ @@ -901,6 +923,8 @@ public static final class Builder implements UrlStage, _FinalStage { private Optional dictation = Optional.empty(); + private Optional diarizeModel = Optional.empty(); + private Optional diarize = Optional.empty(); private Optional detectLanguage = Optional.empty(); @@ -965,6 +989,7 @@ public Builder from(ListenV1RequestUrl other) { detectEntities(other.getDetectEntities()); detectLanguage(other.getDetectLanguage()); diarize(other.getDiarize()); + diarizeModel(other.getDiarizeModel()); dictation(other.getDictation()); encoding(other.getEncoding()); fillerWords(other.getFillerWords()); @@ -1333,6 +1358,26 @@ public _FinalStage dictation(Optional dictation) { return this; } + /** + *

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 Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage diarizeModel(MediaTranscribeRequestDiarizeModel diarizeModel) { + this.diarizeModel = Optional.ofNullable(diarizeModel); + return this; + } + + /** + *

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.

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

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. @@ -1783,6 +1828,7 @@ public ListenV1RequestUrl build() { detectEntities, detectLanguage, diarize, + diarizeModel, dictation, encoding, fillerWords, 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 df4b6bb..078a697 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 @@ -7,6 +7,7 @@ import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestCallbackMethod; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestCustomIntentMode; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestCustomTopicMode; +import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestDiarizeModel; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestEncoding; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestModel; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestSummarize; @@ -69,6 +70,8 @@ public final class MediaTranscribeRequestOctetStream { private final Optional diarize; + private final Optional diarizeModel; + private final Optional dictation; private final Optional encoding; @@ -127,6 +130,7 @@ private MediaTranscribeRequestOctetStream( Optional detectEntities, Optional detectLanguage, Optional diarize, + Optional diarizeModel, Optional dictation, Optional encoding, Optional fillerWords, @@ -165,6 +169,7 @@ private MediaTranscribeRequestOctetStream( this.detectEntities = detectEntities; this.detectLanguage = detectLanguage; this.diarize = diarize; + this.diarizeModel = diarizeModel; this.dictation = dictation; this.encoding = encoding; this.fillerWords = fillerWords; @@ -338,6 +343,14 @@ 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. + */ + @JsonIgnore + public Optional getDiarizeModel() { + return diarizeModel; + } + /** * @return Dictation mode for controlling formatting with dictated speech */ @@ -510,6 +523,7 @@ private boolean equalTo(MediaTranscribeRequestOctetStream other) { && detectEntities.equals(other.detectEntities) && detectLanguage.equals(other.detectLanguage) && diarize.equals(other.diarize) + && diarizeModel.equals(other.diarizeModel) && dictation.equals(other.dictation) && encoding.equals(other.encoding) && fillerWords.equals(other.fillerWords) @@ -552,6 +566,7 @@ public int hashCode() { this.detectEntities, this.detectLanguage, this.diarize, + this.diarizeModel, this.dictation, this.encoding, this.fillerWords, @@ -743,6 +758,13 @@ public interface _FinalStage { _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.

+ */ + _FinalStage diarizeModel(Optional diarizeModel); + + _FinalStage diarizeModel(MediaTranscribeRequestDiarizeModel diarizeModel); + /** *

Dictation mode for controlling formatting with dictated speech

*/ @@ -901,6 +923,8 @@ public static final class Builder implements BodyStage, _FinalStage { private Optional dictation = Optional.empty(); + private Optional diarizeModel = Optional.empty(); + private Optional diarize = Optional.empty(); private Optional detectLanguage = Optional.empty(); @@ -965,6 +989,7 @@ public Builder from(MediaTranscribeRequestOctetStream other) { detectEntities(other.getDetectEntities()); detectLanguage(other.getDetectLanguage()); diarize(other.getDiarize()); + diarizeModel(other.getDiarizeModel()); dictation(other.getDictation()); encoding(other.getEncoding()); fillerWords(other.getFillerWords()); @@ -1333,6 +1358,26 @@ public _FinalStage dictation(Optional dictation) { return this; } + /** + *

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 Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage diarizeModel(MediaTranscribeRequestDiarizeModel diarizeModel) { + this.diarizeModel = Optional.ofNullable(diarizeModel); + return this; + } + + /** + *

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.

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

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. @@ -1783,6 +1828,7 @@ public MediaTranscribeRequestOctetStream build() { detectEntities, detectLanguage, diarize, + diarizeModel, dictation, encoding, fillerWords, diff --git a/src/main/java/com/deepgram/resources/listen/v1/media/types/MediaTranscribeRequestDiarizeModel.java b/src/main/java/com/deepgram/resources/listen/v1/media/types/MediaTranscribeRequestDiarizeModel.java new file mode 100644 index 0000000..e2cd42f --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v1/media/types/MediaTranscribeRequestDiarizeModel.java @@ -0,0 +1,95 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v1.media.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class MediaTranscribeRequestDiarizeModel { + public static final MediaTranscribeRequestDiarizeModel V2 = new MediaTranscribeRequestDiarizeModel(Value.V2, "v2"); + + public static final MediaTranscribeRequestDiarizeModel V1 = new MediaTranscribeRequestDiarizeModel(Value.V1, "v1"); + + public static final MediaTranscribeRequestDiarizeModel LATEST = + new MediaTranscribeRequestDiarizeModel(Value.LATEST, "latest"); + + private final Value value; + + private final String string; + + MediaTranscribeRequestDiarizeModel(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 MediaTranscribeRequestDiarizeModel + && this.string.equals(((MediaTranscribeRequestDiarizeModel) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case V2: + return visitor.visitV2(); + case V1: + return visitor.visitV1(); + case LATEST: + return visitor.visitLatest(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static MediaTranscribeRequestDiarizeModel valueOf(String value) { + switch (value) { + case "v2": + return V2; + case "v1": + return V1; + case "latest": + return LATEST; + default: + return new MediaTranscribeRequestDiarizeModel(Value.UNKNOWN, value); + } + } + + public enum Value { + LATEST, + + V1, + + V2, + + UNKNOWN + } + + public interface Visitor { + T visitLatest(); + + T visitV1(); + + T visitV2(); + + T visitUnknown(String unknownType); + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV1.java b/src/main/java/com/deepgram/types/DeepgramListenProviderV1.java similarity index 79% rename from src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV1.java rename to src/main/java/com/deepgram/types/DeepgramListenProviderV1.java index 55c5633..925c3ac 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV1.java +++ b/src/main/java/com/deepgram/types/DeepgramListenProviderV1.java @@ -1,7 +1,7 @@ /** * This file was auto-generated by Fern from our API Definition. */ -package com.deepgram.resources.agent.v1.types; +package com.deepgram.types; import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; @@ -19,8 +19,10 @@ import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1SettingsAgentContextListenProviderV1.Builder.class) -public final class AgentV1SettingsAgentContextListenProviderV1 { +@JsonDeserialize(builder = DeepgramListenProviderV1.Builder.class) +public final class DeepgramListenProviderV1 { + private final Optional version; + private final Optional model; private final Optional language; @@ -31,12 +33,14 @@ public final class AgentV1SettingsAgentContextListenProviderV1 { private final Map additionalProperties; - private AgentV1SettingsAgentContextListenProviderV1( + private DeepgramListenProviderV1( + Optional version, Optional model, Optional language, Optional> keyterms, Optional smartFormat, Map additionalProperties) { + this.version = version; this.model = model; this.language = language; this.keyterms = keyterms; @@ -52,6 +56,14 @@ public String getType() { return "deepgram"; } + /** + * @return Specifies usage of the V1 Deepgram speech-to-text API + */ + @JsonProperty("version") + public Optional getVersion() { + return version; + } + /** * @return Model to use for speech to text using the V1 API (e.g. Nova-3, Nova-2) */ @@ -87,8 +99,7 @@ public Optional getSmartFormat() { @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof AgentV1SettingsAgentContextListenProviderV1 - && equalTo((AgentV1SettingsAgentContextListenProviderV1) other); + return other instanceof DeepgramListenProviderV1 && equalTo((DeepgramListenProviderV1) other); } @JsonAnyGetter @@ -96,8 +107,9 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(AgentV1SettingsAgentContextListenProviderV1 other) { - return model.equals(other.model) + private boolean equalTo(DeepgramListenProviderV1 other) { + return version.equals(other.version) + && model.equals(other.model) && language.equals(other.language) && keyterms.equals(other.keyterms) && smartFormat.equals(other.smartFormat); @@ -105,7 +117,7 @@ private boolean equalTo(AgentV1SettingsAgentContextListenProviderV1 other) { @java.lang.Override public int hashCode() { - return Objects.hash(this.model, this.language, this.keyterms, this.smartFormat); + return Objects.hash(this.version, this.model, this.language, this.keyterms, this.smartFormat); } @java.lang.Override @@ -119,6 +131,8 @@ public static Builder builder() { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder { + private Optional version = Optional.empty(); + private Optional model = Optional.empty(); private Optional language = Optional.empty(); @@ -132,7 +146,8 @@ public static final class Builder { private Builder() {} - public Builder from(AgentV1SettingsAgentContextListenProviderV1 other) { + public Builder from(DeepgramListenProviderV1 other) { + version(other.getVersion()); model(other.getModel()); language(other.getLanguage()); keyterms(other.getKeyterms()); @@ -140,6 +155,20 @@ public Builder from(AgentV1SettingsAgentContextListenProviderV1 other) { return this; } + /** + *

Specifies usage of the V1 Deepgram speech-to-text API

+ */ + @JsonSetter(value = "version", nulls = Nulls.SKIP) + public Builder version(Optional version) { + this.version = version; + return this; + } + + public Builder version(String version) { + this.version = Optional.ofNullable(version); + return this; + } + /** *

Model to use for speech to text using the V1 API (e.g. Nova-3, Nova-2)

*/ @@ -196,9 +225,8 @@ public Builder smartFormat(Boolean smartFormat) { return this; } - public AgentV1SettingsAgentContextListenProviderV1 build() { - return new AgentV1SettingsAgentContextListenProviderV1( - model, language, keyterms, smartFormat, additionalProperties); + public DeepgramListenProviderV1 build() { + return new DeepgramListenProviderV1(version, model, language, keyterms, smartFormat, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV2.java b/src/main/java/com/deepgram/types/DeepgramListenProviderV2.java similarity index 69% rename from src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV2.java rename to src/main/java/com/deepgram/types/DeepgramListenProviderV2.java index e6f0a07..7f76b77 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV2.java +++ b/src/main/java/com/deepgram/types/DeepgramListenProviderV2.java @@ -1,7 +1,7 @@ /** * This file was auto-generated by Fern from our API Definition. */ -package com.deepgram.resources.agent.v1.types; +package com.deepgram.types; import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; @@ -20,21 +20,25 @@ import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1SettingsAgentContextListenProviderV2.Builder.class) -public final class AgentV1SettingsAgentContextListenProviderV2 { +@JsonDeserialize(builder = DeepgramListenProviderV2.Builder.class) +public final class DeepgramListenProviderV2 { + private final Optional version; + private final String model; - private final Optional languageHint; + private final Optional languageHint; private final Optional> keyterms; private final Map additionalProperties; - private AgentV1SettingsAgentContextListenProviderV2( + private DeepgramListenProviderV2( + Optional version, String model, - Optional languageHint, + Optional languageHint, Optional> keyterms, Map additionalProperties) { + this.version = version; this.model = model; this.languageHint = languageHint; this.keyterms = keyterms; @@ -49,6 +53,14 @@ public String getType() { return "deepgram"; } + /** + * @return Specifies usage of the V2 Deepgram speech-to-text API (e.g. Flux) + */ + @JsonProperty("version") + public Optional getVersion() { + return version; + } + /** * @return Model to use for speech to text using the V2 API (e.g. flux-general-en, flux-general-multi) */ @@ -61,7 +73,7 @@ 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. */ @JsonProperty("language_hint") - public Optional getLanguageHint() { + public Optional getLanguageHint() { return languageHint; } @@ -76,8 +88,7 @@ public Optional> getKeyterms() { @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof AgentV1SettingsAgentContextListenProviderV2 - && equalTo((AgentV1SettingsAgentContextListenProviderV2) other); + return other instanceof DeepgramListenProviderV2 && equalTo((DeepgramListenProviderV2) other); } @JsonAnyGetter @@ -85,13 +96,16 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(AgentV1SettingsAgentContextListenProviderV2 other) { - return model.equals(other.model) && languageHint.equals(other.languageHint) && keyterms.equals(other.keyterms); + private boolean equalTo(DeepgramListenProviderV2 other) { + return version.equals(other.version) + && model.equals(other.model) + && languageHint.equals(other.languageHint) + && keyterms.equals(other.keyterms); } @java.lang.Override public int hashCode() { - return Objects.hash(this.model, this.languageHint, this.keyterms); + return Objects.hash(this.version, this.model, this.languageHint, this.keyterms); } @java.lang.Override @@ -109,22 +123,29 @@ public interface ModelStage { */ _FinalStage model(@NotNull String model); - Builder from(AgentV1SettingsAgentContextListenProviderV2 other); + Builder from(DeepgramListenProviderV2 other); } public interface _FinalStage { - AgentV1SettingsAgentContextListenProviderV2 build(); + DeepgramListenProviderV2 build(); _FinalStage additionalProperty(String key, Object value); _FinalStage additionalProperties(Map additionalProperties); + /** + *

Specifies usage of the V2 Deepgram speech-to-text API (e.g. Flux)

+ */ + _FinalStage version(Optional version); + + _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.

*/ - _FinalStage languageHint(Optional languageHint); + _FinalStage languageHint(Optional languageHint); - _FinalStage languageHint(AgentV1SettingsAgentContextListenProviderV2LanguageHint languageHint); + _FinalStage languageHint(DeepgramListenProviderV2LanguageHint languageHint); /** *

Prompt keyterm recognition to improve Keyword Recall Rate

@@ -140,7 +161,9 @@ public static final class Builder implements ModelStage, _FinalStage { private Optional> keyterms = Optional.empty(); - private Optional languageHint = Optional.empty(); + private Optional languageHint = Optional.empty(); + + private Optional version = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -148,7 +171,8 @@ public static final class Builder implements ModelStage, _FinalStage { private Builder() {} @java.lang.Override - public Builder from(AgentV1SettingsAgentContextListenProviderV2 other) { + public Builder from(DeepgramListenProviderV2 other) { + version(other.getVersion()); model(other.getModel()); languageHint(other.getLanguageHint()); keyterms(other.getKeyterms()); @@ -192,7 +216,7 @@ public _FinalStage keyterms(Optional> keyterms) { * @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override - public _FinalStage languageHint(AgentV1SettingsAgentContextListenProviderV2LanguageHint languageHint) { + public _FinalStage languageHint(DeepgramListenProviderV2LanguageHint languageHint) { this.languageHint = Optional.ofNullable(languageHint); return this; } @@ -202,15 +226,34 @@ public _FinalStage languageHint(AgentV1SettingsAgentContextListenProviderV2Langu */ @java.lang.Override @JsonSetter(value = "language_hint", nulls = Nulls.SKIP) - public _FinalStage languageHint( - Optional languageHint) { + public _FinalStage languageHint(Optional languageHint) { this.languageHint = languageHint; return this; } + /** + *

Specifies usage of the V2 Deepgram speech-to-text API (e.g. Flux)

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

Specifies usage of the V2 Deepgram speech-to-text API (e.g. Flux)

+ */ + @java.lang.Override + @JsonSetter(value = "version", nulls = Nulls.SKIP) + public _FinalStage version(Optional version) { + this.version = version; + return this; + } + @java.lang.Override - public AgentV1SettingsAgentContextListenProviderV2 build() { - return new AgentV1SettingsAgentContextListenProviderV2(model, languageHint, keyterms, additionalProperties); + public DeepgramListenProviderV2 build() { + return new DeepgramListenProviderV2(version, model, languageHint, keyterms, additionalProperties); } @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV2LanguageHint.java b/src/main/java/com/deepgram/types/DeepgramListenProviderV2LanguageHint.java similarity index 62% rename from src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV2LanguageHint.java rename to src/main/java/com/deepgram/types/DeepgramListenProviderV2LanguageHint.java index abcf8bb..cc07718 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProviderV2LanguageHint.java +++ b/src/main/java/com/deepgram/types/DeepgramListenProviderV2LanguageHint.java @@ -1,7 +1,7 @@ /** * This file was auto-generated by Fern from our API Definition. */ -package com.deepgram.resources.agent.v1.types; +package com.deepgram.types; import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonValue; @@ -15,13 +15,13 @@ import java.util.List; import java.util.Objects; -@JsonDeserialize(using = AgentV1SettingsAgentContextListenProviderV2LanguageHint.Deserializer.class) -public final class AgentV1SettingsAgentContextListenProviderV2LanguageHint { +@JsonDeserialize(using = DeepgramListenProviderV2LanguageHint.Deserializer.class) +public final class DeepgramListenProviderV2LanguageHint { private final Object value; private final int type; - private AgentV1SettingsAgentContextListenProviderV2LanguageHint(Object value, int type) { + private DeepgramListenProviderV2LanguageHint(Object value, int type) { this.value = value; this.type = type; } @@ -44,11 +44,11 @@ public T visit(Visitor visitor) { @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof AgentV1SettingsAgentContextListenProviderV2LanguageHint - && equalTo((AgentV1SettingsAgentContextListenProviderV2LanguageHint) other); + return other instanceof DeepgramListenProviderV2LanguageHint + && equalTo((DeepgramListenProviderV2LanguageHint) other); } - private boolean equalTo(AgentV1SettingsAgentContextListenProviderV2LanguageHint other) { + private boolean equalTo(DeepgramListenProviderV2LanguageHint other) { return value.equals(other.value); } @@ -62,12 +62,12 @@ public String toString() { return this.value.toString(); } - public static AgentV1SettingsAgentContextListenProviderV2LanguageHint of(String value) { - return new AgentV1SettingsAgentContextListenProviderV2LanguageHint(value, 0); + public static DeepgramListenProviderV2LanguageHint of(String value) { + return new DeepgramListenProviderV2LanguageHint(value, 0); } - public static AgentV1SettingsAgentContextListenProviderV2LanguageHint of(List value) { - return new AgentV1SettingsAgentContextListenProviderV2LanguageHint(value, 1); + public static DeepgramListenProviderV2LanguageHint of(List value) { + return new DeepgramListenProviderV2LanguageHint(value, 1); } public interface Visitor { @@ -76,14 +76,14 @@ public interface Visitor { T visit(List value); } - static final class Deserializer extends StdDeserializer { + static final class Deserializer extends StdDeserializer { Deserializer() { - super(AgentV1SettingsAgentContextListenProviderV2LanguageHint.class); + super(DeepgramListenProviderV2LanguageHint.class); } @java.lang.Override - public AgentV1SettingsAgentContextListenProviderV2LanguageHint deserialize( - JsonParser p, DeserializationContext context) throws IOException { + 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)); From 80798587481b96483f0ede330101f72b4525a12d Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Thu, 14 May 2026 13:12:28 +0100 Subject: [PATCH 4/4] chore: re-apply manual patches after regen --- .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 bb2bcfc..89993e2 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.4.1"); + put("User-Agent", "com.deepgram:deepgram-java-sdk/0.4.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.4.1"); + put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); + put("X-Fern-SDK-Version", "0.4.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 89993e2..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.4.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.4.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); - } - } - } -}