From 8b70bab1ef540b221a65606746ffc3510a51775c Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 10:11:23 +0100 Subject: [PATCH 01/12] chore: initialize SDK regeneration branch From 380a199148d78ee71004c1483f38b7c3900d55ad Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 10:13:15 +0100 Subject: [PATCH 02/12] chore: unfreeze files pending regen --- .fernignore | 40 +- .../com/deepgram/core/ClientOptions.java.bak | 308 +++++++++ .../ReconnectingWebSocketListener.java.bak | 545 +++++++++++++++ .../v1/types/AgentV1AgentAudioDone.java.bak | 86 +++ .../agent/v1/types/AgentV1KeepAlive.java.bak | 86 +++ .../v1/types/AgentV1ListenUpdated.java.bak | 86 +++ .../v1/types/AgentV1PromptUpdated.java.bak | 86 +++ ...ettingsAgentContextListenProvider.java.bak | 229 +++++++ ...gentV1SettingsAgentListenProvider.java.bak | 229 +++++++ .../v1/types/AgentV1SettingsApplied.java.bak | 86 +++ .../v1/types/AgentV1SpeakUpdated.java.bak | 86 +++ .../v1/types/AgentV1ThinkUpdated.java.bak | 86 +++ ...AgentV1UpdateListenListenProvider.java.bak | 229 +++++++ .../types/AgentV1UserStartedSpeaking.java.bak | 86 +++ .../v1/websocket/V1WebSocketClient.java.bak | 555 ++++++++++++++++ .../v2/types/ListenV2CloseStream.java.bak | 86 +++ .../v2/websocket/V2WebSocketClient.java.bak | 508 ++++++++++++++ .../v1/websocket/V1WebSocketClient.java.bak | 458 +++++++++++++ .../speak/v2/types/SpeakV2Close.java.bak | 86 +++ .../speak/v2/types/SpeakV2Flush.java.bak | 86 +++ .../v2/websocket/V2WebSocketClient.java.bak | 629 ++++++++++++++++++ 21 files changed, 4656 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/deepgram/core/ClientOptions.java.bak create mode 100644 src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak create mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak create mode 100644 src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak create mode 100644 src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak create mode 100644 src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak create mode 100644 src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak create mode 100644 src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak create mode 100644 src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak create mode 100644 src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak diff --git a/.fernignore b/.fernignore index 70ccf7a..3a5eb2f 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 # Forward-compat patch: Fern's generated dispatcher routes any unrecognized message # type to the error handler ("Update your SDK version..."), which makes benign new @@ -32,8 +32,8 @@ src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java # NOTE: the listen v2 and speak v2 clients below also carry the streaming query-param patches # (multi-value serialization + the additionalProperties escape hatch) documented lower down — # keep them frozen until those fixes are upstreamed too. -src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java -src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java +src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak +src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak # Streaming query-param patches on the generated WS connect() builders. Two fixes: # (1) Multi-value serialization: array-valued params (listen: keyterm, keywords, replace, search, @@ -47,24 +47,24 @@ src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java # listen v2 and speak v2 clients are frozen above; freeze the listen v1 and speak v1 clients here too # so the fixes survive regen. Re-apply after regen; unfreeze once the generator emits array params as # repeats and serializes additionalProperties on the WS connect path (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java -src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java +src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak +src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak # Manual equals/hashCode contract fix: Fern generates equals() (all instances equal) but no # hashCode() for fields-less message types, violating the Object contract. We add a consistent # hashCode() to each. Unfreeze and drop these patches once the generator emits a matching # equals/hashCode pair for fields-less types (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java -src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java -src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java +src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak +src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak +src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak # Union default-variant fix (STOPGAP). The agent listen-provider unions declare `version` as an # optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits). Fern @@ -73,9 +73,9 @@ src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java # {"provider":null}, silently dropping the caller's provider on the wire. Patched to # defaultImpl = V2Value on each. Guarded by AgentSettingsProviderDefaultTest. Unfreeze and drop once # the generator stops defaulting unions to the empty _UnknownValue (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak # Build and project configuration build.gradle diff --git a/src/main/java/com/deepgram/core/ClientOptions.java.bak b/src/main/java/com/deepgram/core/ClientOptions.java.bak new file mode 100644 index 0000000..61be390 --- /dev/null +++ b/src/main/java/com/deepgram/core/ClientOptions.java.bak @@ -0,0 +1,308 @@ +/** + * 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 initialRetryDelayMillis; + + private final Optional maxRetryDelayMillis; + + private final Optional retryJitterFactor; + + private final Optional webSocketFactory; + + private final Optional logging; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout, + int maxRetries, + Optional initialRetryDelayMillis, + Optional maxRetryDelayMillis, + Optional retryJitterFactor, + Optional webSocketFactory, + Optional logging) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("User-Agent", "com.deepgram:deepgram-java-sdk/0.8.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.8.0"); // x-release-please-version + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + this.maxRetries = maxRetries; + this.initialRetryDelayMillis = initialRetryDelayMillis; + this.maxRetryDelayMillis = maxRetryDelayMillis; + this.retryJitterFactor = retryJitterFactor; + this.webSocketFactory = webSocketFactory; + this.logging = logging; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public int maxRetries() { + return this.maxRetries; + } + + public Optional initialRetryDelayMillis() { + return this.initialRetryDelayMillis; + } + + public Optional maxRetryDelayMillis() { + return this.maxRetryDelayMillis; + } + + public Optional retryJitterFactor() { + return this.retryJitterFactor; + } + + public Optional webSocketFactory() { + return this.webSocketFactory; + } + + public Optional logging() { + return this.logging; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private int maxRetries = 2; + + private Optional initialRetryDelayMillis = Optional.empty(); + + private Optional maxRetryDelayMillis = Optional.empty(); + + private Optional retryJitterFactor = Optional.empty(); + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + private Optional logging = Optional.empty(); + + private Optional webSocketFactory = Optional.empty(); + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + if (value != null) { + this.headers.put(key, value); + } + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + /** + * Override the initial delay (in milliseconds) used for exponential backoff between retries. Defaults to 1000 milliseconds. + */ + public Builder initialRetryDelayMillis(long initialRetryDelayMillis) { + this.initialRetryDelayMillis = Optional.of(initialRetryDelayMillis); + return this; + } + + /** + * Override the maximum delay (in milliseconds) between retries. Defaults to 60000 milliseconds. + */ + public Builder maxRetryDelayMillis(long maxRetryDelayMillis) { + this.maxRetryDelayMillis = Optional.of(maxRetryDelayMillis); + return this; + } + + /** + * Override the jitter factor (between 0 and 1) applied to retry delays. Defaults to 0.2. + */ + public Builder retryJitterFactor(double retryJitterFactor) { + this.retryJitterFactor = Optional.of(retryJitterFactor); + return this; + } + + 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, + this.initialRetryDelayMillis, + this.maxRetryDelayMillis, + this.retryJitterFactor)); + } + + Logger logger = Logger.from(this.logging); + httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); + httpClientBuilder.addInterceptor(new ResponseDecompressionInterceptor()); + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions( + environment, + headers, + headerSuppliers, + httpClient, + this.timeout.get(), + this.maxRetries, + this.initialRetryDelayMillis, + this.maxRetryDelayMillis, + this.retryJitterFactor, + this.webSocketFactory, + this.logging); + } + + /** + * Create a new Builder initialized with values from an existing ClientOptions + */ + public static Builder from(ClientOptions clientOptions) { + Builder builder = new Builder(); + builder.environment = clientOptions.environment(); + builder.timeout = Optional.of(clientOptions.timeout(null)); + builder.httpClient = clientOptions.httpClient(); + builder.headers.putAll(clientOptions.headers); + builder.headerSuppliers.putAll(clientOptions.headerSuppliers); + builder.maxRetries = clientOptions.maxRetries(); + builder.initialRetryDelayMillis = clientOptions.initialRetryDelayMillis(); + builder.maxRetryDelayMillis = clientOptions.maxRetryDelayMillis(); + builder.retryJitterFactor = clientOptions.retryJitterFactor(); + builder.logging = clientOptions.logging(); + return builder; + } + } +} diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak new file mode 100644 index 0000000..a533068 --- /dev/null +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak @@ -0,0 +1,545 @@ +/** + * 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 { + // Overridable options are held behind a single volatile reference (not five separate volatile + // fields) so {@link #applyOptionsOverride} swaps them atomically — a reader that snapshots the + // reference once sees a mutually-consistent set (e.g. getNextDelay() cannot observe a new min + // paired with an old max). Rewiring is used by {@code TransportWebSocketFactory} to honour + // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. + private volatile ReconnectOptions activeOptions; + + // maxEnqueuedMessages is fixed at construction (the queue is sized once) and intentionally not + // overridable, so it stays a separate final field rather than being read from activeOptions. + private final int maxEnqueuedMessages; + + private final AtomicInteger retryCount = new AtomicInteger(0); + + private final AtomicBoolean connectLock = new AtomicBoolean(false); + + private final AtomicBoolean shouldReconnect = new AtomicBoolean(true); + + protected volatile WebSocket webSocket; + + private volatile long connectionEstablishedTime = 0L; + + private final ConcurrentLinkedQueue messageQueue = new ConcurrentLinkedQueue<>(); + + private final ConcurrentLinkedQueue binaryMessageQueue = new ConcurrentLinkedQueue<>(); + + private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(); + + private final Supplier connectionSupplier; + + /** + * Creates a new reconnecting WebSocket listener. + * + * @param options Reconnection configuration options + * @param connectionSupplier Supplier that creates new WebSocket connections + */ + public ReconnectingWebSocketListener( + ReconnectingWebSocketListener.ReconnectOptions options, Supplier connectionSupplier) { + this.activeOptions = options; + this.maxEnqueuedMessages = options.maxEnqueuedMessages; + this.connectionSupplier = connectionSupplier; + } + + /** + * Replaces 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: the override is a single atomic write to a {@code volatile} reference, so a + * reader that snapshots {@link #activeOptions} once sees a mutually-consistent set of values + * (never a new min paired with an old max). 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.activeOptions = options; + } + + /** + * Initiates a WebSocket connection with automatic reconnection enabled. + * + * Connection behavior: + * - Times out after {@code ReconnectOptions.connectionTimeoutMs} (default 4000ms) + * - Thread-safe via atomic lock (returns immediately if connection in progress) + * - {@code maxRetries} counts retries only — 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; + } + // Snapshot the overridable options once so the gate and timeout below use a consistent set. + ReconnectOptions opts = this.activeOptions; + // 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() > opts.maxRetries) { + connectLock.set(false); + return; + } + try { + CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); + try { + webSocket = connectionFuture.get(opts.connectionTimeoutMs, MILLISECONDS); + } catch (TimeoutException e) { + connectionFuture.cancel(true); + TimeoutException timeoutError = + new TimeoutException("WebSocket connection timeout after " + opts.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() { + // Single volatile read → a consistent (min, growFactor, max) snapshot even if + // applyOptionsOverride swaps the options concurrently. + ReconnectOptions opts = this.activeOptions; + if (retryCount.get() == 1) { + return opts.minReconnectionDelayMs; + } + long delay = (long) (opts.minReconnectionDelayMs * Math.pow(opts.reconnectionDelayGrowFactor, retryCount.get() - 1)); + return Math.min(delay, opts.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); + } + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak new file mode 100644 index 0000000..41af522 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak @@ -0,0 +1,86 @@ +/** + * 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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1AgentAudioDone.Builder.class) +public final class AgentV1AgentAudioDone { + private final Map additionalProperties; + + private AgentV1AgentAudioDone(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier indicating the agent has finished sending audio + */ + @JsonProperty("type") + public String getType() { + return "AgentAudioDone"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1AgentAudioDone; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1AgentAudioDone other) { + return this; + } + + public AgentV1AgentAudioDone build() { + return new AgentV1AgentAudioDone(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak new file mode 100644 index 0000000..083578b --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak @@ -0,0 +1,86 @@ +/** + * 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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1KeepAlive.Builder.class) +public final class AgentV1KeepAlive { + private final Map additionalProperties; + + private AgentV1KeepAlive(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "KeepAlive"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1KeepAlive; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1KeepAlive other) { + return this; + } + + public AgentV1KeepAlive build() { + return new AgentV1KeepAlive(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak new file mode 100644 index 0000000..6018473 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak @@ -0,0 +1,86 @@ +/** + * 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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1ListenUpdated.Builder.class) +public final class AgentV1ListenUpdated { + private final Map additionalProperties; + + private AgentV1ListenUpdated(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for listen update confirmation + */ + @JsonProperty("type") + public String getType() { + return "ListenUpdated"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1ListenUpdated; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1ListenUpdated other) { + return this; + } + + public AgentV1ListenUpdated build() { + return new AgentV1ListenUpdated(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak new file mode 100644 index 0000000..6edec37 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak @@ -0,0 +1,86 @@ +/** + * 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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1PromptUpdated.Builder.class) +public final class AgentV1PromptUpdated { + private final Map additionalProperties; + + private AgentV1PromptUpdated(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for prompt update confirmation + */ + @JsonProperty("type") + public String getType() { + return "PromptUpdated"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1PromptUpdated; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1PromptUpdated other) { + return this; + } + + public AgentV1PromptUpdated build() { + return new AgentV1PromptUpdated(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak new file mode 100644 index 0000000..c4ee1de --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak @@ -0,0 +1,229 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +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; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Objects; +import java.util.Optional; + +public final class AgentV1SettingsAgentContextListenProvider { + private final Value value; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + private AgentV1SettingsAgentContextListenProvider(Value value) { + this.value = value; + } + + public T visit(Visitor visitor) { + return value.visit(visitor); + } + + public static AgentV1SettingsAgentContextListenProvider v1(DeepgramListenProviderV1 value) { + return new AgentV1SettingsAgentContextListenProvider(new V1Value(value)); + } + + public static AgentV1SettingsAgentContextListenProvider v2(DeepgramListenProviderV2 value) { + return new AgentV1SettingsAgentContextListenProvider(new V2Value(value)); + } + + public boolean isV1() { + return value instanceof V1Value; + } + + public boolean isV2() { + return value instanceof V2Value; + } + + public boolean _isUnknown() { + return value instanceof _UnknownValue; + } + + public Optional getV1() { + if (isV1()) { + return Optional.of(((V1Value) value).value); + } + return Optional.empty(); + } + + public Optional getV2() { + if (isV2()) { + return Optional.of(((V2Value) value).value); + } + return Optional.empty(); + } + + public Optional _getUnknown() { + if (_isUnknown()) { + return Optional.of(((_UnknownValue) value).value); + } + return Optional.empty(); + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1SettingsAgentContextListenProvider + && value.equals(((AgentV1SettingsAgentContextListenProvider) other).value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return value.toString(); + } + + @JsonValue + private Value getValue() { + return this.value; + } + + public interface Visitor { + T visitV1(DeepgramListenProviderV1 v1); + + T visitV2(DeepgramListenProviderV2 v2); + + T _visitUnknown(Object unknownType); + } + + // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an + // empty body, so a provider payload omitting the optional "version" discriminator (exactly what + // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes + // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop + // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) + @JsonIgnoreProperties(ignoreUnknown = true) + private interface Value { + T visit(Visitor visitor); + } + + @JsonTypeName("v1") + @JsonIgnoreProperties("version") + private static final class V1Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV1 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V1Value() {} + + private V1Value(DeepgramListenProviderV1 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV1(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1Value && equalTo((V1Value) other); + } + + private boolean equalTo(V1Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentContextListenProvider{" + "value: " + value + "}"; + } + } + + @JsonTypeName("v2") + @JsonIgnoreProperties("version") + private static final class V2Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV2 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V2Value() {} + + private V2Value(DeepgramListenProviderV2 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV2(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V2Value && equalTo((V2Value) other); + } + + private boolean equalTo(V2Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentContextListenProvider{" + "value: " + value + "}"; + } + } + + @JsonIgnoreProperties("version") + private static final class _UnknownValue implements Value { + private String type; + + @JsonValue + private Object value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private _UnknownValue(@JsonProperty("value") Object value) {} + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor._visitUnknown(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof _UnknownValue && equalTo((_UnknownValue) other); + } + + private boolean equalTo(_UnknownValue other) { + return type.equals(other.type) && value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.type, this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentContextListenProvider{" + "type: " + type + ", value: " + value + "}"; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak new file mode 100644 index 0000000..3bf0333 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak @@ -0,0 +1,229 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +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; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Objects; +import java.util.Optional; + +public final class AgentV1SettingsAgentListenProvider { + private final Value value; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + private AgentV1SettingsAgentListenProvider(Value value) { + this.value = value; + } + + public T visit(Visitor visitor) { + return value.visit(visitor); + } + + public static AgentV1SettingsAgentListenProvider v1(DeepgramListenProviderV1 value) { + return new AgentV1SettingsAgentListenProvider(new V1Value(value)); + } + + public static AgentV1SettingsAgentListenProvider v2(DeepgramListenProviderV2 value) { + return new AgentV1SettingsAgentListenProvider(new V2Value(value)); + } + + public boolean isV1() { + return value instanceof V1Value; + } + + public boolean isV2() { + return value instanceof V2Value; + } + + public boolean _isUnknown() { + return value instanceof _UnknownValue; + } + + public Optional getV1() { + if (isV1()) { + return Optional.of(((V1Value) value).value); + } + return Optional.empty(); + } + + public Optional getV2() { + if (isV2()) { + return Optional.of(((V2Value) value).value); + } + return Optional.empty(); + } + + public Optional _getUnknown() { + if (_isUnknown()) { + return Optional.of(((_UnknownValue) value).value); + } + return Optional.empty(); + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1SettingsAgentListenProvider + && value.equals(((AgentV1SettingsAgentListenProvider) other).value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return value.toString(); + } + + @JsonValue + private Value getValue() { + return this.value; + } + + public interface Visitor { + T visitV1(DeepgramListenProviderV1 v1); + + T visitV2(DeepgramListenProviderV2 v2); + + T _visitUnknown(Object unknownType); + } + + // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an + // empty body, so a provider payload omitting the optional "version" discriminator (exactly what + // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes + // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop + // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) + @JsonIgnoreProperties(ignoreUnknown = true) + private interface Value { + T visit(Visitor visitor); + } + + @JsonTypeName("v1") + @JsonIgnoreProperties("version") + private static final class V1Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV1 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V1Value() {} + + private V1Value(DeepgramListenProviderV1 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV1(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1Value && equalTo((V1Value) other); + } + + private boolean equalTo(V1Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentListenProvider{" + "value: " + value + "}"; + } + } + + @JsonTypeName("v2") + @JsonIgnoreProperties("version") + private static final class V2Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV2 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V2Value() {} + + private V2Value(DeepgramListenProviderV2 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV2(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V2Value && equalTo((V2Value) other); + } + + private boolean equalTo(V2Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentListenProvider{" + "value: " + value + "}"; + } + } + + @JsonIgnoreProperties("version") + private static final class _UnknownValue implements Value { + private String type; + + @JsonValue + private Object value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private _UnknownValue(@JsonProperty("value") Object value) {} + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor._visitUnknown(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof _UnknownValue && equalTo((_UnknownValue) other); + } + + private boolean equalTo(_UnknownValue other) { + return type.equals(other.type) && value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.type, this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1SettingsAgentListenProvider{" + "type: " + type + ", value: " + value + "}"; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak new file mode 100644 index 0000000..fb23c60 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak @@ -0,0 +1,86 @@ +/** + * 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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1SettingsApplied.Builder.class) +public final class AgentV1SettingsApplied { + private final Map additionalProperties; + + private AgentV1SettingsApplied(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for settings applied confirmation + */ + @JsonProperty("type") + public String getType() { + return "SettingsApplied"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1SettingsApplied; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1SettingsApplied other) { + return this; + } + + public AgentV1SettingsApplied build() { + return new AgentV1SettingsApplied(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak new file mode 100644 index 0000000..7619157 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak @@ -0,0 +1,86 @@ +/** + * 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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1SpeakUpdated.Builder.class) +public final class AgentV1SpeakUpdated { + private final Map additionalProperties; + + private AgentV1SpeakUpdated(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for speak update confirmation + */ + @JsonProperty("type") + public String getType() { + return "SpeakUpdated"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1SpeakUpdated; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1SpeakUpdated other) { + return this; + } + + public AgentV1SpeakUpdated build() { + return new AgentV1SpeakUpdated(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak new file mode 100644 index 0000000..2f2a981 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak @@ -0,0 +1,86 @@ +/** + * 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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1ThinkUpdated.Builder.class) +public final class AgentV1ThinkUpdated { + private final Map additionalProperties; + + private AgentV1ThinkUpdated(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier for think update confirmation + */ + @JsonProperty("type") + public String getType() { + return "ThinkUpdated"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1ThinkUpdated; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1ThinkUpdated other) { + return this; + } + + public AgentV1ThinkUpdated build() { + return new AgentV1ThinkUpdated(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak new file mode 100644 index 0000000..a0b84d9 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak @@ -0,0 +1,229 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +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; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Objects; +import java.util.Optional; + +public final class AgentV1UpdateListenListenProvider { + private final Value value; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + private AgentV1UpdateListenListenProvider(Value value) { + this.value = value; + } + + public T visit(Visitor visitor) { + return value.visit(visitor); + } + + public static AgentV1UpdateListenListenProvider v1(DeepgramListenProviderV1 value) { + return new AgentV1UpdateListenListenProvider(new V1Value(value)); + } + + public static AgentV1UpdateListenListenProvider v2(DeepgramListenProviderV2 value) { + return new AgentV1UpdateListenListenProvider(new V2Value(value)); + } + + public boolean isV1() { + return value instanceof V1Value; + } + + public boolean isV2() { + return value instanceof V2Value; + } + + public boolean _isUnknown() { + return value instanceof _UnknownValue; + } + + public Optional getV1() { + if (isV1()) { + return Optional.of(((V1Value) value).value); + } + return Optional.empty(); + } + + public Optional getV2() { + if (isV2()) { + return Optional.of(((V2Value) value).value); + } + return Optional.empty(); + } + + public Optional _getUnknown() { + if (_isUnknown()) { + return Optional.of(((_UnknownValue) value).value); + } + return Optional.empty(); + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1UpdateListenListenProvider + && value.equals(((AgentV1UpdateListenListenProvider) other).value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return value.toString(); + } + + @JsonValue + private Value getValue() { + return this.value; + } + + public interface Visitor { + T visitV1(DeepgramListenProviderV1 v1); + + T visitV2(DeepgramListenProviderV2 v2); + + T _visitUnknown(Object unknownType); + } + + // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an + // empty body, so a provider payload omitting the optional "version" discriminator (exactly what + // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes + // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop + // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) + @JsonIgnoreProperties(ignoreUnknown = true) + private interface Value { + T visit(Visitor visitor); + } + + @JsonTypeName("v1") + @JsonIgnoreProperties("version") + private static final class V1Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV1 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V1Value() {} + + private V1Value(DeepgramListenProviderV1 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV1(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1Value && equalTo((V1Value) other); + } + + private boolean equalTo(V1Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1UpdateListenListenProvider{" + "value: " + value + "}"; + } + } + + @JsonTypeName("v2") + @JsonIgnoreProperties("version") + private static final class V2Value implements Value { + @JsonUnwrapped + @JsonIgnoreProperties(value = "version", allowSetters = true) + private DeepgramListenProviderV2 value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private V2Value() {} + + private V2Value(DeepgramListenProviderV2 value) { + this.value = value; + } + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor.visitV2(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V2Value && equalTo((V2Value) other); + } + + private boolean equalTo(V2Value other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1UpdateListenListenProvider{" + "value: " + value + "}"; + } + } + + @JsonIgnoreProperties("version") + private static final class _UnknownValue implements Value { + private String type; + + @JsonValue + private Object value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + private _UnknownValue(@JsonProperty("value") Object value) {} + + @java.lang.Override + public T visit(Visitor visitor) { + return visitor._visitUnknown(value); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof _UnknownValue && equalTo((_UnknownValue) other); + } + + private boolean equalTo(_UnknownValue other) { + return type.equals(other.type) && value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.type, this.value); + } + + @java.lang.Override + public String toString() { + return "AgentV1UpdateListenListenProvider{" + "type: " + type + ", value: " + value + "}"; + } + } +} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak new file mode 100644 index 0000000..7c3fc38 --- /dev/null +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak @@ -0,0 +1,86 @@ +/** + * 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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AgentV1UserStartedSpeaking.Builder.class) +public final class AgentV1UserStartedSpeaking { + private final Map additionalProperties; + + private AgentV1UserStartedSpeaking(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier indicating that the user has begun speaking + */ + @JsonProperty("type") + public String getType() { + return "UserStartedSpeaking"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AgentV1UserStartedSpeaking; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AgentV1UserStartedSpeaking other) { + return this; + } + + public AgentV1UserStartedSpeaking build() { + return new AgentV1UserStartedSpeaking(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak new file mode 100644 index 0000000..ffa4953 --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak @@ -0,0 +1,555 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v1.websocket; + +import com.deepgram.core.ClientOptions; +import com.deepgram.core.DisconnectReason; +import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; +import com.deepgram.core.ReconnectingWebSocketListener; +import com.deepgram.core.RequestOptions; +import com.deepgram.core.WebSocketReadyState; +import com.deepgram.resources.listen.v1.types.ListenV1CloseStream; +import com.deepgram.resources.listen.v1.types.ListenV1Finalize; +import com.deepgram.resources.listen.v1.types.ListenV1KeepAlive; +import com.deepgram.resources.listen.v1.types.ListenV1Metadata; +import com.deepgram.resources.listen.v1.types.ListenV1Results; +import com.deepgram.resources.listen.v1.types.ListenV1SpeechStarted; +import com.deepgram.resources.listen.v1.types.ListenV1UtteranceEnd; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okio.ByteString; + +/** + * WebSocket client for the v1 channel. + * Provides real-time bidirectional communication with strongly-typed messages. + */ +public class V1WebSocketClient implements AutoCloseable { + protected final ClientOptions clientOptions; + + private final ObjectMapper objectMapper; + + private final OkHttpClient okHttpClient; + + private ScheduledExecutorService timeoutExecutor; + + private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; + + private volatile Runnable onConnectedHandler; + + private volatile Consumer onDisconnectedHandler; + + private volatile Consumer onErrorHandler; + + private volatile Consumer onMessageHandler; + + private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; + + private CompletableFuture connectionFuture; + + private ReconnectingWebSocketListener reconnectingListener; + + private volatile Consumer resultsHandler; + + private volatile Consumer metadataHandler; + + private volatile Consumer utteranceEndHandler; + + private volatile Consumer speechStartedHandler; + + /** + * Creates a new async WebSocket client for the v1 channel. + */ + public V1WebSocketClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.objectMapper = ObjectMappers.JSON_MAPPER; + this.okHttpClient = clientOptions.httpClient(); + } + + /** + * Establishes the WebSocket connection asynchronously with automatic reconnection. + * @return a CompletableFuture that completes when the connection is established + * @param options connection options including query parameters + */ + public CompletableFuture connect(V1ConnectOptions options) { + connectionFuture = new CompletableFuture<>(); + String baseUrl = clientOptions.environment().getProductionURL(); + String fullPath = "/v1/listen"; + if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { + fullPath = fullPath.substring(1); + } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { + fullPath = "/" + fullPath; + } + // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing + if (baseUrl.startsWith("wss://")) { + baseUrl = "https://" + baseUrl.substring(6); + } else if (baseUrl.startsWith("ws://")) { + baseUrl = "http://" + baseUrl.substring(5); + } + HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); + if (parsedUrl == null) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); + } + HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); + if (options.getCallback() != null && options.getCallback().isPresent()) { + urlBuilder.addQueryParameter( + "callback", String.valueOf(options.getCallback().get())); + } + if (options.getCallbackMethod() != null && options.getCallbackMethod().isPresent()) { + urlBuilder.addQueryParameter( + "callback_method", + String.valueOf(options.getCallbackMethod().get())); + } + if (options.getChannels() != null && options.getChannels().isPresent()) { + urlBuilder.addQueryParameter( + "channels", String.valueOf(options.getChannels().get())); + } + if (options.getDetectEntities() != null && options.getDetectEntities().isPresent()) { + urlBuilder.addQueryParameter( + "detect_entities", + String.valueOf(options.getDetectEntities().get())); + } + if (options.getDiarize() != null && options.getDiarize().isPresent()) { + urlBuilder.addQueryParameter( + "diarize", String.valueOf(options.getDiarize().get())); + } + if (options.getDiarizeModel() != null && options.getDiarizeModel().isPresent()) { + urlBuilder.addQueryParameter( + "diarize_model", String.valueOf(options.getDiarizeModel().get())); + } + if (options.getDictation() != null && options.getDictation().isPresent()) { + urlBuilder.addQueryParameter( + "dictation", String.valueOf(options.getDictation().get())); + } + if (options.getEncoding() != null && options.getEncoding().isPresent()) { + urlBuilder.addQueryParameter( + "encoding", String.valueOf(options.getEncoding().get())); + } + if (options.getEndpointing() != null && options.getEndpointing().isPresent()) { + urlBuilder.addQueryParameter( + "endpointing", String.valueOf(options.getEndpointing().get())); + } + if (options.getExtra() != null && options.getExtra().isPresent()) { + // Array-valued query params (String | List unions) must serialize as repeated + // params (extra=a&extra=b), not a stringified list. The generated streaming template + // uses String.valueOf(...), which mangles a List into "[a, b]"; route these through + // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. + QueryStringMapper.addQueryParameter( + urlBuilder, "extra", options.getExtra().get().get(), true); + } + if (options.getInterimResults() != null && options.getInterimResults().isPresent()) { + urlBuilder.addQueryParameter( + "interim_results", + String.valueOf(options.getInterimResults().get())); + } + if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { + QueryStringMapper.addQueryParameter( + urlBuilder, "keyterm", options.getKeyterm().get().get(), true); + } + if (options.getKeywords() != null && options.getKeywords().isPresent()) { + QueryStringMapper.addQueryParameter( + urlBuilder, "keywords", options.getKeywords().get().get(), true); + } + if (options.getLanguage() != null && options.getLanguage().isPresent()) { + urlBuilder.addQueryParameter( + "language", String.valueOf(options.getLanguage().get())); + } + if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { + urlBuilder.addQueryParameter( + "mip_opt_out", String.valueOf(options.getMipOptOut().get())); + } + urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); + if (options.getMultichannel() != null && options.getMultichannel().isPresent()) { + urlBuilder.addQueryParameter( + "multichannel", String.valueOf(options.getMultichannel().get())); + } + if (options.getNumerals() != null && options.getNumerals().isPresent()) { + urlBuilder.addQueryParameter( + "numerals", String.valueOf(options.getNumerals().get())); + } + if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { + urlBuilder.addQueryParameter( + "profanity_filter", + String.valueOf(options.getProfanityFilter().get())); + } + if (options.getPunctuate() != null && options.getPunctuate().isPresent()) { + urlBuilder.addQueryParameter( + "punctuate", String.valueOf(options.getPunctuate().get())); + } + if (options.getRedact() != null && options.getRedact().isPresent()) { + urlBuilder.addQueryParameter( + "redact", String.valueOf(options.getRedact().get())); + } + if (options.getReplace() != null && options.getReplace().isPresent()) { + QueryStringMapper.addQueryParameter( + urlBuilder, "replace", options.getReplace().get().get(), true); + } + if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { + urlBuilder.addQueryParameter( + "sample_rate", String.valueOf(options.getSampleRate().get())); + } + if (options.getSearch() != null && options.getSearch().isPresent()) { + QueryStringMapper.addQueryParameter( + urlBuilder, "search", options.getSearch().get().get(), true); + } + if (options.getSmartFormat() != null && options.getSmartFormat().isPresent()) { + urlBuilder.addQueryParameter( + "smart_format", String.valueOf(options.getSmartFormat().get())); + } + if (options.getTag() != null && options.getTag().isPresent()) { + QueryStringMapper.addQueryParameter( + urlBuilder, "tag", options.getTag().get().get(), true); + } + if (options.getUtteranceEndMs() != null && options.getUtteranceEndMs().isPresent()) { + urlBuilder.addQueryParameter( + "utterance_end_ms", + String.valueOf(options.getUtteranceEndMs().get())); + } + if (options.getVadEvents() != null && options.getVadEvents().isPresent()) { + urlBuilder.addQueryParameter( + "vad_events", String.valueOf(options.getVadEvents().get())); + } + if (options.getVersion() != null && options.getVersion().isPresent()) { + urlBuilder.addQueryParameter( + "version", String.valueOf(options.getVersion().get())); + } + // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. + // The generated template only serializes the typed options and drops these otherwise. + // ConnectOptions is request-only (never deserialized), so this map holds only what the + // caller set via the builder. Routed through QueryStringMapper to match the REST path. + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } + Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); + final Request request = requestBuilder.build(); + this.readyState = WebSocketReadyState.CONNECTING; + ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null + ? this.reconnectOptions + : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); + this.reconnectingListener = + new ReconnectingWebSocketListener(reconnectOpts, () -> { + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }) { + @Override + protected void onWebSocketOpen(WebSocket webSocket, Response response) { + readyState = WebSocketReadyState.OPEN; + if (onConnectedHandler != null) { + onConnectedHandler.run(); + } + connectionFuture.complete(null); + } + + @Override + protected void onWebSocketMessage(WebSocket webSocket, String text) { + handleIncomingMessage(text); + } + + @Override + protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) {} + + @Override + protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { + readyState = WebSocketReadyState.CLOSED; + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException(t)); + } + connectionFuture.completeExceptionally(t); + } + + @Override + protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { + readyState = WebSocketReadyState.CLOSED; + if (onDisconnectedHandler != null) { + onDisconnectedHandler.accept(new DisconnectReason(code, reason)); + } + } + }; + reconnectingListener.connect(); + return connectionFuture; + } + + /** + * Disconnects the WebSocket connection and releases resources. + */ + public void disconnect() { + reconnectingListener.disconnect(); + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + } + + /** + * Gets the current state of the WebSocket connection. + * + * This provides the actual connection state, similar to the W3C WebSocket API. + * + * @return the current WebSocket ready state + */ + public WebSocketReadyState getReadyState() { + return readyState; + } + + /** + * Sends a ListenV1Media message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendMedia(ByteString message) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + // Use reconnecting listener's sendBinary method which handles queuing + reconnectingListener.sendBinary(message); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); + } + return future; + } + + /** + * Sends a ListenV1Finalize message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendFinalize(ListenV1Finalize message) { + return sendMessage(message); + } + + /** + * Sends a ListenV1CloseStream message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendCloseStream(ListenV1CloseStream message) { + return sendMessage(message); + } + + /** + * Sends a ListenV1KeepAlive message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendKeepAlive(ListenV1KeepAlive message) { + return sendMessage(message); + } + + /** + * Registers a handler for ListenV1Results messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onResults(Consumer handler) { + this.resultsHandler = handler; + } + + /** + * Registers a handler for ListenV1Metadata messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onMetadata(Consumer handler) { + this.metadataHandler = handler; + } + + /** + * Registers a handler for ListenV1UtteranceEnd messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onUtteranceEnd(Consumer handler) { + this.utteranceEndHandler = handler; + } + + /** + * Registers a handler for ListenV1SpeechStarted messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeechStarted(Consumer handler) { + this.speechStartedHandler = handler; + } + + /** + * Registers a handler called when the connection is established. + * @param handler the handler to invoke when connected + */ + public void onConnected(Runnable handler) { + this.onConnectedHandler = handler; + } + + /** + * Registers a handler called when the connection is closed. + * @param handler the handler to invoke when disconnected + */ + public void onDisconnected(Consumer handler) { + this.onDisconnectedHandler = handler; + } + + /** + * Registers a handler called when an error occurs. + * @param handler the handler to invoke on error + */ + public void onError(Consumer handler) { + this.onErrorHandler = handler; + } + + /** + * Registers a handler called for every incoming text message. + * The handler receives the raw JSON string before type-specific dispatch. + * @param handler the handler to invoke with the raw message JSON + */ + public void onMessage(Consumer handler) { + this.onMessageHandler = handler; + } + + /** + * Configures reconnection behavior. Must be called before {@link #connect}. + * + * @param options the reconnection options (backoff, retries, queue size) + */ + public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { + this.reconnectOptions = options; + } + + /** + * Closes this WebSocket client, releasing all resources. + * Equivalent to calling {@link #disconnect()}. + */ + @Override + public void close() { + disconnect(); + } + + /** + * Ensures the WebSocket is connected and ready to send messages. + * @throws IllegalStateException if the socket is not connected or not open + */ + private void assertSocketIsOpen() { + if (reconnectingListener.getWebSocket() == null) { + throw new IllegalStateException("WebSocket is not connected. Call connect() first."); + } + if (readyState != WebSocketReadyState.OPEN) { + throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); + } + } + + private CompletableFuture sendMessage(Object body) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + String json = objectMapper.writeValueAsString(body); + // Use reconnecting listener's send method which handles queuing + reconnectingListener.send(json); + future.complete(null); + } catch (IllegalStateException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send message", e)); + } + return future; + } + + private void handleIncomingMessage(String json) { + try { + if (onMessageHandler != null) { + onMessageHandler.accept(json); + } + JsonNode node = objectMapper.readTree(json); + if (node == null || node.isNull()) { + throw new IllegalArgumentException("Received null or invalid JSON message"); + } + if (node.has("transaction_key") + && node.has("request_id") + && node.has("sha256") + && node.has("created") + && node.has("duration") + && node.has("channels") + && "Metadata".equals(node.path("type").asText())) { + ListenV1Metadata metadataHandlerEvent = null; + try { + metadataHandlerEvent = objectMapper.treeToValue(node, ListenV1Metadata.class); + } catch (Exception e) { + } + if (metadataHandlerEvent != null) { + if (metadataHandler != null) { + metadataHandler.accept(metadataHandlerEvent); + } + return; + } + } + if (node.has("channel_index") + && node.has("duration") + && node.has("start") + && node.has("channel") + && node.has("metadata") + && "Results".equals(node.path("type").asText())) { + ListenV1Results resultsHandlerEvent = null; + try { + resultsHandlerEvent = objectMapper.treeToValue(node, ListenV1Results.class); + } catch (Exception e) { + } + if (resultsHandlerEvent != null) { + if (resultsHandler != null) { + resultsHandler.accept(resultsHandlerEvent); + } + return; + } + } + if (node.has("channel") + && node.has("last_word_end") + && "UtteranceEnd".equals(node.path("type").asText())) { + ListenV1UtteranceEnd utteranceEndHandlerEvent = null; + try { + utteranceEndHandlerEvent = objectMapper.treeToValue(node, ListenV1UtteranceEnd.class); + } catch (Exception e) { + } + if (utteranceEndHandlerEvent != null) { + if (utteranceEndHandler != null) { + utteranceEndHandler.accept(utteranceEndHandlerEvent); + } + return; + } + } + if (node.has("channel") + && node.has("timestamp") + && "SpeechStarted".equals(node.path("type").asText())) { + ListenV1SpeechStarted speechStartedHandlerEvent = null; + try { + speechStartedHandlerEvent = objectMapper.treeToValue(node, ListenV1SpeechStarted.class); + } catch (Exception e) { + } + if (speechStartedHandlerEvent != null) { + if (speechStartedHandler != null) { + speechStartedHandler.accept(speechStartedHandlerEvent); + } + return; + } + } + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException( + "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) + + "... Update your SDK version to support new message types.")); + } + } catch (Exception e) { + if (onErrorHandler != null) { + onErrorHandler.accept(e); + } + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak new file mode 100644 index 0000000..e508681 --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v2.types; + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ListenV2CloseStream.Builder.class) +public final class ListenV2CloseStream { + private final Map additionalProperties; + + private ListenV2CloseStream(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "CloseStream"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ListenV2CloseStream; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ListenV2CloseStream other) { + return this; + } + + public ListenV2CloseStream build() { + return new ListenV2CloseStream(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak new file mode 100644 index 0000000..63f44e4 --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak @@ -0,0 +1,508 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v2.websocket; + +import com.deepgram.core.ClientOptions; +import com.deepgram.core.DisconnectReason; +import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; +import com.deepgram.core.ReconnectingWebSocketListener; +import com.deepgram.core.RequestOptions; +import com.deepgram.core.WebSocketReadyState; +import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; +import com.deepgram.resources.listen.v2.types.ListenV2Configure; +import com.deepgram.resources.listen.v2.types.ListenV2ConfigureFailure; +import com.deepgram.resources.listen.v2.types.ListenV2ConfigureSuccess; +import com.deepgram.resources.listen.v2.types.ListenV2Connected; +import com.deepgram.resources.listen.v2.types.ListenV2FatalError; +import com.deepgram.resources.listen.v2.types.ListenV2TurnInfo; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okio.ByteString; + +/** + * WebSocket client for the v2 channel. + * Provides real-time bidirectional communication with strongly-typed messages. + */ +public class V2WebSocketClient implements AutoCloseable { + protected final ClientOptions clientOptions; + + private final ObjectMapper objectMapper; + + private final OkHttpClient okHttpClient; + + private ScheduledExecutorService timeoutExecutor; + + private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; + + private volatile Runnable onConnectedHandler; + + private volatile Consumer onDisconnectedHandler; + + private volatile Consumer onErrorHandler; + + private volatile Consumer onMessageHandler; + + private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; + + private CompletableFuture connectionFuture; + + private ReconnectingWebSocketListener reconnectingListener; + + private volatile Consumer connectedHandler; + + private volatile Consumer turnInfoHandler; + + private volatile Consumer configureSuccessHandler; + + private volatile Consumer configureFailureHandler; + + private volatile Consumer errorHandler; + + /** + * Creates a new async WebSocket client for the v2 channel. + */ + public V2WebSocketClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.objectMapper = ObjectMappers.JSON_MAPPER; + this.okHttpClient = clientOptions.httpClient(); + } + + /** + * Establishes the WebSocket connection asynchronously with automatic reconnection. + * @return a CompletableFuture that completes when the connection is established + * @param options connection options including query parameters + */ + public CompletableFuture connect(V2ConnectOptions options) { + connectionFuture = new CompletableFuture<>(); + String baseUrl = clientOptions.environment().getProductionURL(); + String fullPath = "/v2/listen"; + if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { + fullPath = fullPath.substring(1); + } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { + fullPath = "/" + fullPath; + } + // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing + if (baseUrl.startsWith("wss://")) { + baseUrl = "https://" + baseUrl.substring(6); + } else if (baseUrl.startsWith("ws://")) { + baseUrl = "http://" + baseUrl.substring(5); + } + HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); + if (parsedUrl == null) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); + } + HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); + urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); + if (options.getEncoding() != null && options.getEncoding().isPresent()) { + urlBuilder.addQueryParameter( + "encoding", String.valueOf(options.getEncoding().get())); + } + if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { + urlBuilder.addQueryParameter( + "sample_rate", String.valueOf(options.getSampleRate().get())); + } + if (options.getEagerEotThreshold() != null + && options.getEagerEotThreshold().isPresent()) { + urlBuilder.addQueryParameter( + "eager_eot_threshold", + String.valueOf(options.getEagerEotThreshold().get())); + } + if (options.getEotThreshold() != null && options.getEotThreshold().isPresent()) { + urlBuilder.addQueryParameter( + "eot_threshold", String.valueOf(options.getEotThreshold().get())); + } + if (options.getEotTimeoutMs() != null && options.getEotTimeoutMs().isPresent()) { + urlBuilder.addQueryParameter( + "eot_timeout_ms", String.valueOf(options.getEotTimeoutMs().get())); + } + if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { + // Array-valued query params (String | List unions) must serialize as repeated + // params (keyterm=a&keyterm=b), not a stringified list. The generated streaming template + // uses String.valueOf(...), which mangles a List into "[a, b]"; route these through + // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. + QueryStringMapper.addQueryParameter( + urlBuilder, "keyterm", options.getKeyterm().get().get(), true); + } + if (options.getLanguageHint() != null && options.getLanguageHint().isPresent()) { + QueryStringMapper.addQueryParameter( + urlBuilder, "language_hint", options.getLanguageHint().get().get(), true); + } + if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { + urlBuilder.addQueryParameter( + "profanity_filter", + String.valueOf(options.getProfanityFilter().get())); + } + if (options.getNumerals() != null && options.getNumerals().isPresent()) { + urlBuilder.addQueryParameter( + "numerals", String.valueOf(options.getNumerals().get())); + } + if (options.getRedact() != null && options.getRedact().isPresent()) { + urlBuilder.addQueryParameter( + "redact", String.valueOf(options.getRedact().get())); + } + if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { + urlBuilder.addQueryParameter( + "mip_opt_out", String.valueOf(options.getMipOptOut().get())); + } + if (options.getTag() != null && options.getTag().isPresent()) { + QueryStringMapper.addQueryParameter( + urlBuilder, "tag", options.getTag().get().get(), true); + } + // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. + // The generated template only serializes the typed options and drops these otherwise. + // ConnectOptions is request-only (never deserialized), so this map holds only what the + // caller set via the builder. Routed through QueryStringMapper to match the REST path. + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } + Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); + final Request request = requestBuilder.build(); + this.readyState = WebSocketReadyState.CONNECTING; + ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null + ? this.reconnectOptions + : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); + this.reconnectingListener = + new ReconnectingWebSocketListener(reconnectOpts, () -> { + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }) { + @Override + protected void onWebSocketOpen(WebSocket webSocket, Response response) { + readyState = WebSocketReadyState.OPEN; + if (onConnectedHandler != null) { + onConnectedHandler.run(); + } + connectionFuture.complete(null); + } + + @Override + protected void onWebSocketMessage(WebSocket webSocket, String text) { + handleIncomingMessage(text); + } + + @Override + protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) {} + + @Override + protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { + readyState = WebSocketReadyState.CLOSED; + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException(t)); + } + connectionFuture.completeExceptionally(t); + } + + @Override + protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { + readyState = WebSocketReadyState.CLOSED; + if (onDisconnectedHandler != null) { + onDisconnectedHandler.accept(new DisconnectReason(code, reason)); + } + } + }; + reconnectingListener.connect(); + return connectionFuture; + } + + /** + * Disconnects the WebSocket connection and releases resources. + */ + public void disconnect() { + reconnectingListener.disconnect(); + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + } + + /** + * Gets the current state of the WebSocket connection. + * + * This provides the actual connection state, similar to the W3C WebSocket API. + * + * @return the current WebSocket ready state + */ + public WebSocketReadyState getReadyState() { + return readyState; + } + + /** + * Sends a ListenV2Media message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendMedia(ByteString message) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + // Use reconnecting listener's sendBinary method which handles queuing + reconnectingListener.sendBinary(message); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); + } + return future; + } + + /** + * Sends a ListenV2CloseStream message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendCloseStream(ListenV2CloseStream message) { + return sendMessage(message); + } + + /** + * Sends a ListenV2Configure message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendConfigure(ListenV2Configure message) { + return sendMessage(message); + } + + /** + * Registers a handler for ListenV2Connected messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConnected(Consumer handler) { + this.connectedHandler = handler; + } + + /** + * Registers a handler for ListenV2TurnInfo messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onTurnInfo(Consumer handler) { + this.turnInfoHandler = handler; + } + + /** + * Registers a handler for ListenV2ConfigureSuccess messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConfigureSuccess(Consumer handler) { + this.configureSuccessHandler = handler; + } + + /** + * Registers a handler for ListenV2ConfigureFailure messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConfigureFailure(Consumer handler) { + this.configureFailureHandler = handler; + } + + /** + * Registers a handler for ListenV2FatalError messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onErrorMessage(Consumer handler) { + this.errorHandler = handler; + } + + /** + * Registers a handler called when the connection is established. + * @param handler the handler to invoke when connected + */ + public void onConnected(Runnable handler) { + this.onConnectedHandler = handler; + } + + /** + * Registers a handler called when the connection is closed. + * @param handler the handler to invoke when disconnected + */ + public void onDisconnected(Consumer handler) { + this.onDisconnectedHandler = handler; + } + + /** + * Registers a handler called when an error occurs. + * @param handler the handler to invoke on error + */ + public void onError(Consumer handler) { + this.onErrorHandler = handler; + } + + /** + * Registers a handler called for every incoming text message. + * The handler receives the raw JSON string before type-specific dispatch. + * @param handler the handler to invoke with the raw message JSON + */ + public void onMessage(Consumer handler) { + this.onMessageHandler = handler; + } + + /** + * Configures reconnection behavior. Must be called before {@link #connect}. + * + * @param options the reconnection options (backoff, retries, queue size) + */ + public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { + this.reconnectOptions = options; + } + + /** + * Closes this WebSocket client, releasing all resources. + * Equivalent to calling {@link #disconnect()}. + */ + @Override + public void close() { + disconnect(); + } + + /** + * Ensures the WebSocket is connected and ready to send messages. + * @throws IllegalStateException if the socket is not connected or not open + */ + private void assertSocketIsOpen() { + if (reconnectingListener.getWebSocket() == null) { + throw new IllegalStateException("WebSocket is not connected. Call connect() first."); + } + if (readyState != WebSocketReadyState.OPEN) { + throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); + } + } + + private CompletableFuture sendMessage(Object body) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + String json = objectMapper.writeValueAsString(body); + // Use reconnecting listener's send method which handles queuing + reconnectingListener.send(json); + future.complete(null); + } catch (IllegalStateException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send message", e)); + } + return future; + } + + private void handleIncomingMessage(String json) { + try { + if (onMessageHandler != null) { + onMessageHandler.accept(json); + } + JsonNode node = objectMapper.readTree(json); + if (node == null || node.isNull()) { + throw new IllegalArgumentException("Received null or invalid JSON message"); + } + if (node.has("request_id") + && node.has("sequence_id") + && node.has("event") + && node.has("turn_index") + && node.has("audio_window_start") + && node.has("audio_window_end") + && node.has("transcript") + && node.has("words") + && node.has("end_of_turn_confidence") + && "TurnInfo".equals(node.path("type").asText())) { + ListenV2TurnInfo turnInfoHandlerEvent = null; + try { + turnInfoHandlerEvent = objectMapper.treeToValue(node, ListenV2TurnInfo.class); + } catch (Exception e) { + } + if (turnInfoHandlerEvent != null) { + if (turnInfoHandler != null) { + turnInfoHandler.accept(turnInfoHandlerEvent); + } + return; + } + } + if (node.has("request_id") + && node.has("thresholds") + && node.has("keyterms") + && node.has("sequence_id") + && "ConfigureSuccess".equals(node.path("type").asText())) { + ListenV2ConfigureSuccess configureSuccessHandlerEvent = null; + try { + configureSuccessHandlerEvent = objectMapper.treeToValue(node, ListenV2ConfigureSuccess.class); + } catch (Exception e) { + } + if (configureSuccessHandlerEvent != null) { + if (configureSuccessHandler != null) { + configureSuccessHandler.accept(configureSuccessHandlerEvent); + } + return; + } + } + if (node.has("sequence_id") + && node.has("code") + && node.has("description") + && "Error".equals(node.path("type").asText())) { + ListenV2FatalError errorHandlerEvent = null; + try { + errorHandlerEvent = objectMapper.treeToValue(node, ListenV2FatalError.class); + } catch (Exception e) { + } + if (errorHandlerEvent != null) { + if (errorHandler != null) { + errorHandler.accept(errorHandlerEvent); + } + return; + } + } + if (node.has("request_id") + && node.has("sequence_id") + && "Connected".equals(node.path("type").asText())) { + ListenV2Connected connectedHandlerEvent = null; + try { + connectedHandlerEvent = objectMapper.treeToValue(node, ListenV2Connected.class); + } catch (Exception e) { + } + if (connectedHandlerEvent != null) { + if (connectedHandler != null) { + connectedHandler.accept(connectedHandlerEvent); + } + return; + } + } + if (node.has("request_id") + && node.has("sequence_id") + && "ConfigureFailure".equals(node.path("type").asText())) { + ListenV2ConfigureFailure configureFailureHandlerEvent = null; + try { + configureFailureHandlerEvent = objectMapper.treeToValue(node, ListenV2ConfigureFailure.class); + } catch (Exception e) { + } + if (configureFailureHandlerEvent != null) { + if (configureFailureHandler != null) { + configureFailureHandler.accept(configureFailureHandlerEvent); + } + return; + } + } + // Unrecognized message type: forward-compatible no-op. The raw frame was + // already delivered to onMessage(String) above, so a newer server adding a + // benign control frame (e.g. a GA addition to this endpoint) must not surface + // as a fatal error to deployed clients. Routing it to onError would make a + // harmless frame look fatal; mirrors the speak v2 client and the JS/Python SDKs. + } catch (Exception e) { + if (onErrorHandler != null) { + onErrorHandler.accept(e); + } + } + } +} diff --git a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak new file mode 100644 index 0000000..8033a55 --- /dev/null +++ b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak @@ -0,0 +1,458 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.speak.v1.websocket; + +import com.deepgram.core.ClientOptions; +import com.deepgram.core.DisconnectReason; +import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; +import com.deepgram.core.ReconnectingWebSocketListener; +import com.deepgram.core.RequestOptions; +import com.deepgram.core.WebSocketReadyState; +import com.deepgram.resources.speak.v1.types.SpeakV1Clear; +import com.deepgram.resources.speak.v1.types.SpeakV1Cleared; +import com.deepgram.resources.speak.v1.types.SpeakV1Close; +import com.deepgram.resources.speak.v1.types.SpeakV1Flush; +import com.deepgram.resources.speak.v1.types.SpeakV1Flushed; +import com.deepgram.resources.speak.v1.types.SpeakV1Metadata; +import com.deepgram.resources.speak.v1.types.SpeakV1Text; +import com.deepgram.resources.speak.v1.types.SpeakV1Warning; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okio.ByteString; + +/** + * WebSocket client for the v1 channel. + * Provides real-time bidirectional communication with strongly-typed messages. + */ +public class V1WebSocketClient implements AutoCloseable { + protected final ClientOptions clientOptions; + + private final ObjectMapper objectMapper; + + private final OkHttpClient okHttpClient; + + private ScheduledExecutorService timeoutExecutor; + + private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; + + private volatile Runnable onConnectedHandler; + + private volatile Consumer onDisconnectedHandler; + + private volatile Consumer onErrorHandler; + + private volatile Consumer onMessageHandler; + + private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; + + private CompletableFuture connectionFuture; + + private ReconnectingWebSocketListener reconnectingListener; + + private volatile Consumer speakV1AudioHandler; + + private volatile Consumer metadataHandler; + + private volatile Consumer flushedHandler; + + private volatile Consumer clearedHandler; + + private volatile Consumer warningHandler; + + /** + * Creates a new async WebSocket client for the v1 channel. + */ + public V1WebSocketClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.objectMapper = ObjectMappers.JSON_MAPPER; + this.okHttpClient = clientOptions.httpClient(); + } + + /** + * Establishes the WebSocket connection asynchronously with automatic reconnection. + * @return a CompletableFuture that completes when the connection is established + * @param options connection options including query parameters + */ + public CompletableFuture connect(V1ConnectOptions options) { + connectionFuture = new CompletableFuture<>(); + String baseUrl = clientOptions.environment().getProductionURL(); + String fullPath = "/v1/speak"; + if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { + fullPath = fullPath.substring(1); + } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { + fullPath = "/" + fullPath; + } + // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing + if (baseUrl.startsWith("wss://")) { + baseUrl = "https://" + baseUrl.substring(6); + } else if (baseUrl.startsWith("ws://")) { + baseUrl = "http://" + baseUrl.substring(5); + } + HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); + if (parsedUrl == null) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); + } + HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); + if (options.getEncoding() != null && options.getEncoding().isPresent()) { + urlBuilder.addQueryParameter( + "encoding", String.valueOf(options.getEncoding().get())); + } + if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { + urlBuilder.addQueryParameter( + "mip_opt_out", String.valueOf(options.getMipOptOut().get())); + } + if (options.getModel() != null && options.getModel().isPresent()) { + urlBuilder.addQueryParameter( + "model", String.valueOf(options.getModel().get())); + } + if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { + urlBuilder.addQueryParameter( + "sample_rate", String.valueOf(options.getSampleRate().get())); + } + if (options.getSpeed() != null && options.getSpeed().isPresent()) { + urlBuilder.addQueryParameter( + "speed", String.valueOf(options.getSpeed().get())); + } + // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. + // The generated template only serializes the typed options and drops these otherwise. + // ConnectOptions is request-only (never deserialized), so this map holds only what the + // caller set via the builder. Routed through QueryStringMapper to match the REST path. + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } + Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); + final Request request = requestBuilder.build(); + this.readyState = WebSocketReadyState.CONNECTING; + ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null + ? this.reconnectOptions + : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); + this.reconnectingListener = + new ReconnectingWebSocketListener(reconnectOpts, () -> { + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }) { + @Override + protected void onWebSocketOpen(WebSocket webSocket, Response response) { + readyState = WebSocketReadyState.OPEN; + if (onConnectedHandler != null) { + onConnectedHandler.run(); + } + connectionFuture.complete(null); + } + + @Override + protected void onWebSocketMessage(WebSocket webSocket, String text) { + handleIncomingMessage(text); + } + + @Override + protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { + if (speakV1AudioHandler != null) { + speakV1AudioHandler.accept(bytes); + } + } + + @Override + protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { + readyState = WebSocketReadyState.CLOSED; + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException(t)); + } + connectionFuture.completeExceptionally(t); + } + + @Override + protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { + readyState = WebSocketReadyState.CLOSED; + if (onDisconnectedHandler != null) { + onDisconnectedHandler.accept(new DisconnectReason(code, reason)); + } + } + }; + reconnectingListener.connect(); + return connectionFuture; + } + + /** + * Establishes the WebSocket connection asynchronously with default options. + * @return a CompletableFuture that completes when the connection is established + */ + public CompletableFuture connect() { + return connect(V1ConnectOptions.builder().build()); + } + + /** + * Disconnects the WebSocket connection and releases resources. + */ + public void disconnect() { + reconnectingListener.disconnect(); + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + } + + /** + * Gets the current state of the WebSocket connection. + * + * This provides the actual connection state, similar to the W3C WebSocket API. + * + * @return the current WebSocket ready state + */ + public WebSocketReadyState getReadyState() { + return readyState; + } + + /** + * Sends a SpeakV1Text message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendText(SpeakV1Text message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV1Flush message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendFlush(SpeakV1Flush message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV1Clear message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendClear(SpeakV1Clear message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV1Close message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendClose(SpeakV1Close message) { + return sendMessage(message); + } + + /** + * Registers a handler for SpeakV1Audio messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeakV1Audio(Consumer handler) { + this.speakV1AudioHandler = handler; + } + + /** + * Registers a handler for SpeakV1Metadata messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onMetadata(Consumer handler) { + this.metadataHandler = handler; + } + + /** + * Registers a handler for SpeakV1Flushed messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onFlushed(Consumer handler) { + this.flushedHandler = handler; + } + + /** + * Registers a handler for SpeakV1Cleared messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onCleared(Consumer handler) { + this.clearedHandler = handler; + } + + /** + * Registers a handler for SpeakV1Warning messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onWarning(Consumer handler) { + this.warningHandler = handler; + } + + /** + * Registers a handler called when the connection is established. + * @param handler the handler to invoke when connected + */ + public void onConnected(Runnable handler) { + this.onConnectedHandler = handler; + } + + /** + * Registers a handler called when the connection is closed. + * @param handler the handler to invoke when disconnected + */ + public void onDisconnected(Consumer handler) { + this.onDisconnectedHandler = handler; + } + + /** + * Registers a handler called when an error occurs. + * @param handler the handler to invoke on error + */ + public void onError(Consumer handler) { + this.onErrorHandler = handler; + } + + /** + * Registers a handler called for every incoming text message. + * The handler receives the raw JSON string before type-specific dispatch. + * @param handler the handler to invoke with the raw message JSON + */ + public void onMessage(Consumer handler) { + this.onMessageHandler = handler; + } + + /** + * Configures reconnection behavior. Must be called before {@link #connect}. + * + * @param options the reconnection options (backoff, retries, queue size) + */ + public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { + this.reconnectOptions = options; + } + + /** + * Closes this WebSocket client, releasing all resources. + * Equivalent to calling {@link #disconnect()}. + */ + @Override + public void close() { + disconnect(); + } + + /** + * Ensures the WebSocket is connected and ready to send messages. + * @throws IllegalStateException if the socket is not connected or not open + */ + private void assertSocketIsOpen() { + if (reconnectingListener.getWebSocket() == null) { + throw new IllegalStateException("WebSocket is not connected. Call connect() first."); + } + if (readyState != WebSocketReadyState.OPEN) { + throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); + } + } + + private CompletableFuture sendMessage(Object body) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + String json = objectMapper.writeValueAsString(body); + // Use reconnecting listener's send method which handles queuing + reconnectingListener.send(json); + future.complete(null); + } catch (IllegalStateException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send message", e)); + } + return future; + } + + private void handleIncomingMessage(String json) { + try { + if (onMessageHandler != null) { + onMessageHandler.accept(json); + } + JsonNode node = objectMapper.readTree(json); + if (node == null || node.isNull()) { + throw new IllegalArgumentException("Received null or invalid JSON message"); + } + if (node.has("request_id") + && node.has("model_name") + && node.has("model_version") + && node.has("model_uuid") + && "Metadata".equals(node.path("type").asText())) { + SpeakV1Metadata metadataHandlerEvent = null; + try { + metadataHandlerEvent = objectMapper.treeToValue(node, SpeakV1Metadata.class); + } catch (Exception e) { + } + if (metadataHandlerEvent != null) { + if (metadataHandler != null) { + metadataHandler.accept(metadataHandlerEvent); + } + return; + } + } + if (node.has("description") + && node.has("code") + && "Warning".equals(node.path("type").asText())) { + SpeakV1Warning warningHandlerEvent = null; + try { + warningHandlerEvent = objectMapper.treeToValue(node, SpeakV1Warning.class); + } catch (Exception e) { + } + if (warningHandlerEvent != null) { + if (warningHandler != null) { + warningHandler.accept(warningHandlerEvent); + } + return; + } + } + if (node.has("type") && node.has("sequence_id")) { + SpeakV1Flushed flushedHandlerEvent = null; + try { + flushedHandlerEvent = objectMapper.treeToValue(node, SpeakV1Flushed.class); + } catch (Exception e) { + } + if (flushedHandlerEvent != null) { + if (flushedHandler != null) { + flushedHandler.accept(flushedHandlerEvent); + } + return; + } + } + if (node.has("type") && node.has("sequence_id")) { + SpeakV1Cleared clearedHandlerEvent = null; + try { + clearedHandlerEvent = objectMapper.treeToValue(node, SpeakV1Cleared.class); + } catch (Exception e) { + } + if (clearedHandlerEvent != null) { + if (clearedHandler != null) { + clearedHandler.accept(clearedHandlerEvent); + } + return; + } + } + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException( + "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) + + "... Update your SDK version to support new message types.")); + } + } catch (Exception e) { + if (onErrorHandler != null) { + onErrorHandler.accept(e); + } + } + } +} diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak new file mode 100644 index 0000000..51fef19 --- /dev/null +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.speak.v2.types; + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = SpeakV2Close.Builder.class) +public final class SpeakV2Close { + private final Map additionalProperties; + + private SpeakV2Close(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "Close"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof SpeakV2Close; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(SpeakV2Close other) { + return this; + } + + public SpeakV2Close build() { + return new SpeakV2Close(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak new file mode 100644 index 0000000..e122400 --- /dev/null +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.speak.v2.types; + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = SpeakV2Flush.Builder.class) +public final class SpeakV2Flush { + private final Map additionalProperties; + + private SpeakV2Flush(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "Flush"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof SpeakV2Flush; + } + + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(SpeakV2Flush other) { + return this; + } + + public SpeakV2Flush build() { + return new SpeakV2Flush(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak new file mode 100644 index 0000000..2a8dc12 --- /dev/null +++ b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak @@ -0,0 +1,629 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.speak.v2.websocket; + +import com.deepgram.core.ClientOptions; +import com.deepgram.core.DisconnectReason; +import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; +import com.deepgram.core.ReconnectingWebSocketListener; +import com.deepgram.core.RequestOptions; +import com.deepgram.core.WebSocketReadyState; +import com.deepgram.resources.speak.v2.types.SpeakV2Close; +import com.deepgram.resources.speak.v2.types.SpeakV2Configure; +import com.deepgram.resources.speak.v2.types.SpeakV2ConfigureFailure; +import com.deepgram.resources.speak.v2.types.SpeakV2ConfigureSuccess; +import com.deepgram.resources.speak.v2.types.SpeakV2Connected; +import com.deepgram.resources.speak.v2.types.SpeakV2Error; +import com.deepgram.resources.speak.v2.types.SpeakV2Flush; +import com.deepgram.resources.speak.v2.types.SpeakV2Flushed; +import com.deepgram.resources.speak.v2.types.SpeakV2Interrupt; +import com.deepgram.resources.speak.v2.types.SpeakV2SessionMetadata; +import com.deepgram.resources.speak.v2.types.SpeakV2Speak; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechInterrupted; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechMetadata; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechStarted; +import com.deepgram.resources.speak.v2.types.SpeakV2Warning; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okio.ByteString; + +/** + * WebSocket client for the v2 channel. + * Provides real-time bidirectional communication with strongly-typed messages. + */ +public class V2WebSocketClient implements AutoCloseable { + protected final ClientOptions clientOptions; + + private final ObjectMapper objectMapper; + + private final OkHttpClient okHttpClient; + + private ScheduledExecutorService timeoutExecutor; + + private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; + + private volatile Runnable onConnectedHandler; + + private volatile Consumer onDisconnectedHandler; + + private volatile Consumer onErrorHandler; + + private volatile Consumer onMessageHandler; + + private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; + + private CompletableFuture connectionFuture; + + private ReconnectingWebSocketListener reconnectingListener; + + private volatile Consumer speakV2AudioHandler; + + private volatile Consumer connectedHandler; + + private volatile Consumer speechStartedHandler; + + private volatile Consumer speechMetadataHandler; + + private volatile Consumer speechInterruptedHandler; + + private volatile Consumer flushedHandler; + + private volatile Consumer sessionMetadataHandler; + + private volatile Consumer configureSuccessHandler; + + private volatile Consumer configureFailureHandler; + + private volatile Consumer warningHandler; + + private volatile Consumer errorHandler; + + /** + * Creates a new async WebSocket client for the v2 channel. + */ + public V2WebSocketClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.objectMapper = ObjectMappers.JSON_MAPPER; + this.okHttpClient = clientOptions.httpClient(); + } + + /** + * Establishes the WebSocket connection asynchronously with automatic reconnection. + * @return a CompletableFuture that completes when the connection is established + * @param options connection options including query parameters + */ + public CompletableFuture connect(V2ConnectOptions options) { + connectionFuture = new CompletableFuture<>(); + String baseUrl = clientOptions.environment().getProductionURL(); + String fullPath = "/v2/speak"; + if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { + fullPath = fullPath.substring(1); + } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { + fullPath = "/" + fullPath; + } + // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing + if (baseUrl.startsWith("wss://")) { + baseUrl = "https://" + baseUrl.substring(6); + } else if (baseUrl.startsWith("ws://")) { + baseUrl = "http://" + baseUrl.substring(5); + } + HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); + if (parsedUrl == null) { + throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); + } + HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); + urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); + if (options.getEncoding() != null && options.getEncoding().isPresent()) { + urlBuilder.addQueryParameter( + "encoding", String.valueOf(options.getEncoding().get())); + } + if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { + urlBuilder.addQueryParameter( + "sample_rate", String.valueOf(options.getSampleRate().get())); + } + if (options.getSpeed() != null && options.getSpeed().isPresent()) { + urlBuilder.addQueryParameter( + "speed", String.valueOf(options.getSpeed().get())); + } + if (options.getExpressivity() != null && options.getExpressivity().isPresent()) { + urlBuilder.addQueryParameter( + "expressivity", String.valueOf(options.getExpressivity().get())); + } + if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { + urlBuilder.addQueryParameter( + "mip_opt_out", String.valueOf(options.getMipOptOut().get())); + } + if (options.getTag() != null && options.getTag().isPresent()) { + // Array-valued query params (String | List unions) must serialize as repeated + // params (tag=a&tag=b), not a stringified list. The generated streaming template uses + // String.valueOf(...), which mangles a List into "[a, b]"; route through + // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. + QueryStringMapper.addQueryParameter( + urlBuilder, "tag", options.getTag().get().get(), true); + } + // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. + // The generated template only serializes the typed options and drops these otherwise. + // ConnectOptions is request-only (never deserialized), so this map holds only what the + // caller set via the builder. Routed through QueryStringMapper to match the REST path. + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } + Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); + clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); + final Request request = requestBuilder.build(); + this.readyState = WebSocketReadyState.CONNECTING; + ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null + ? this.reconnectOptions + : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); + this.reconnectingListener = + new ReconnectingWebSocketListener(reconnectOpts, () -> { + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }) { + @Override + protected void onWebSocketOpen(WebSocket webSocket, Response response) { + readyState = WebSocketReadyState.OPEN; + if (onConnectedHandler != null) { + onConnectedHandler.run(); + } + connectionFuture.complete(null); + } + + @Override + protected void onWebSocketMessage(WebSocket webSocket, String text) { + handleIncomingMessage(text); + } + + @Override + protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { + if (speakV2AudioHandler != null) { + speakV2AudioHandler.accept(bytes); + } + } + + @Override + protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { + readyState = WebSocketReadyState.CLOSED; + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException(t)); + } + connectionFuture.completeExceptionally(t); + } + + @Override + protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { + readyState = WebSocketReadyState.CLOSED; + if (onDisconnectedHandler != null) { + onDisconnectedHandler.accept(new DisconnectReason(code, reason)); + } + } + }; + reconnectingListener.connect(); + return connectionFuture; + } + + /** + * Disconnects the WebSocket connection and releases resources. + */ + public void disconnect() { + reconnectingListener.disconnect(); + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + } + + /** + * Gets the current state of the WebSocket connection. + * + * This provides the actual connection state, similar to the W3C WebSocket API. + * + * @return the current WebSocket ready state + */ + public WebSocketReadyState getReadyState() { + return readyState; + } + + /** + * Sends a SpeakV2Speak message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendSpeak(SpeakV2Speak message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV2Flush message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendFlush(SpeakV2Flush message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV2Interrupt message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendInterrupt(SpeakV2Interrupt message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV2Configure message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendConfigure(SpeakV2Configure message) { + return sendMessage(message); + } + + /** + * Sends a SpeakV2Close message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendClose(SpeakV2Close message) { + return sendMessage(message); + } + + /** + * Registers a handler for SpeakV2Audio messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeakV2Audio(Consumer handler) { + this.speakV2AudioHandler = handler; + } + + /** + * Registers a handler for SpeakV2Connected messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConnected(Consumer handler) { + this.connectedHandler = handler; + } + + /** + * Registers a handler for SpeakV2SpeechStarted messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeechStarted(Consumer handler) { + this.speechStartedHandler = handler; + } + + /** + * Registers a handler for SpeakV2SpeechMetadata messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeechMetadata(Consumer handler) { + this.speechMetadataHandler = handler; + } + + /** + * Registers a handler for SpeakV2SpeechInterrupted messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSpeechInterrupted(Consumer handler) { + this.speechInterruptedHandler = handler; + } + + /** + * Registers a handler for SpeakV2Flushed messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onFlushed(Consumer handler) { + this.flushedHandler = handler; + } + + /** + * Registers a handler for SpeakV2SessionMetadata messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onSessionMetadata(Consumer handler) { + this.sessionMetadataHandler = handler; + } + + /** + * Registers a handler for SpeakV2ConfigureSuccess messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConfigureSuccess(Consumer handler) { + this.configureSuccessHandler = handler; + } + + /** + * Registers a handler for SpeakV2ConfigureFailure messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onConfigureFailure(Consumer handler) { + this.configureFailureHandler = handler; + } + + /** + * Registers a handler for SpeakV2Warning messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onWarning(Consumer handler) { + this.warningHandler = handler; + } + + /** + * Registers a handler for SpeakV2Error messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onErrorMessage(Consumer handler) { + this.errorHandler = handler; + } + + /** + * Registers a handler called when the connection is established. + * @param handler the handler to invoke when connected + */ + public void onConnected(Runnable handler) { + this.onConnectedHandler = handler; + } + + /** + * Registers a handler called when the connection is closed. + * @param handler the handler to invoke when disconnected + */ + public void onDisconnected(Consumer handler) { + this.onDisconnectedHandler = handler; + } + + /** + * Registers a handler called when an error occurs. + * @param handler the handler to invoke on error + */ + public void onError(Consumer handler) { + this.onErrorHandler = handler; + } + + /** + * Registers a handler called for every incoming text message. + * The handler receives the raw JSON string before type-specific dispatch. + * @param handler the handler to invoke with the raw message JSON + */ + public void onMessage(Consumer handler) { + this.onMessageHandler = handler; + } + + /** + * Configures reconnection behavior. Must be called before {@link #connect}. + * + * @param options the reconnection options (backoff, retries, queue size) + */ + public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { + this.reconnectOptions = options; + } + + /** + * Closes this WebSocket client, releasing all resources. + * Equivalent to calling {@link #disconnect()}. + */ + @Override + public void close() { + disconnect(); + } + + /** + * Ensures the WebSocket is connected and ready to send messages. + * @throws IllegalStateException if the socket is not connected or not open + */ + private void assertSocketIsOpen() { + if (reconnectingListener.getWebSocket() == null) { + throw new IllegalStateException("WebSocket is not connected. Call connect() first."); + } + if (readyState != WebSocketReadyState.OPEN) { + throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); + } + } + + private CompletableFuture sendMessage(Object body) { + CompletableFuture future = new CompletableFuture<>(); + try { + assertSocketIsOpen(); + String json = objectMapper.writeValueAsString(body); + // Use reconnecting listener's send method which handles queuing + reconnectingListener.send(json); + future.complete(null); + } catch (IllegalStateException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(new RuntimeException("Failed to send message", e)); + } + return future; + } + + private void handleIncomingMessage(String json) { + try { + if (onMessageHandler != null) { + onMessageHandler.accept(json); + } + JsonNode node = objectMapper.readTree(json); + if (node == null || node.isNull()) { + throw new IllegalArgumentException("Received null or invalid JSON message"); + } + if (node.has("speech_id") + && node.has("audio_duration_ms") + && node.has("input_character_count") + && node.has("billable_character_count") + && node.has("controls_applied") + && "SpeechMetadata".equals(node.path("type").asText())) { + SpeakV2SpeechMetadata speechMetadataHandlerEvent = null; + try { + speechMetadataHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechMetadata.class); + } catch (Exception e) { + } + if (speechMetadataHandlerEvent != null) { + if (speechMetadataHandler != null) { + speechMetadataHandler.accept(speechMetadataHandlerEvent); + } + return; + } + } + if (node.has("request_id") + && node.has("model_name") + && node.has("model_version") + && node.has("model_uuids") + && "Connected".equals(node.path("type").asText())) { + SpeakV2Connected connectedHandlerEvent = null; + try { + connectedHandlerEvent = objectMapper.treeToValue(node, SpeakV2Connected.class); + } catch (Exception e) { + } + if (connectedHandlerEvent != null) { + if (connectedHandler != null) { + connectedHandler.accept(connectedHandlerEvent); + } + return; + } + } + if (node.has("total_audio_duration_ms") + && node.has("total_input_character_count") + && node.has("total_billable_character_count") + && "SessionMetadata".equals(node.path("type").asText())) { + SpeakV2SessionMetadata sessionMetadataHandlerEvent = null; + try { + sessionMetadataHandlerEvent = objectMapper.treeToValue(node, SpeakV2SessionMetadata.class); + } catch (Exception e) { + } + if (sessionMetadataHandlerEvent != null) { + if (sessionMetadataHandler != null) { + sessionMetadataHandler.accept(sessionMetadataHandlerEvent); + } + return; + } + } + if (node.has("audio_played_ms") + && node.has("metadata") + && "SpeechInterrupted".equals(node.path("type").asText())) { + SpeakV2SpeechInterrupted speechInterruptedHandlerEvent = null; + try { + speechInterruptedHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechInterrupted.class); + } catch (Exception e) { + } + if (speechInterruptedHandlerEvent != null) { + if (speechInterruptedHandler != null) { + speechInterruptedHandler.accept(speechInterruptedHandlerEvent); + } + return; + } + } + if (node.has("code") + && node.has("description") + && "ConfigureFailure".equals(node.path("type").asText())) { + SpeakV2ConfigureFailure configureFailureHandlerEvent = null; + try { + configureFailureHandlerEvent = objectMapper.treeToValue(node, SpeakV2ConfigureFailure.class); + } catch (Exception e) { + } + if (configureFailureHandlerEvent != null) { + if (configureFailureHandler != null) { + configureFailureHandler.accept(configureFailureHandlerEvent); + } + return; + } + } + if (node.has("code") + && node.has("description") + && "Warning".equals(node.path("type").asText())) { + SpeakV2Warning warningHandlerEvent = null; + try { + warningHandlerEvent = objectMapper.treeToValue(node, SpeakV2Warning.class); + } catch (Exception e) { + } + if (warningHandlerEvent != null) { + if (warningHandler != null) { + warningHandler.accept(warningHandlerEvent); + } + return; + } + } + if (node.has("code") + && node.has("description") + && "Error".equals(node.path("type").asText())) { + SpeakV2Error errorHandlerEvent = null; + try { + errorHandlerEvent = objectMapper.treeToValue(node, SpeakV2Error.class); + } catch (Exception e) { + } + if (errorHandlerEvent != null) { + if (errorHandler != null) { + errorHandler.accept(errorHandlerEvent); + } + return; + } + } + if (node.has("speech_id") + && "SpeechStarted".equals(node.path("type").asText())) { + SpeakV2SpeechStarted speechStartedHandlerEvent = null; + try { + speechStartedHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechStarted.class); + } catch (Exception e) { + } + if (speechStartedHandlerEvent != null) { + if (speechStartedHandler != null) { + speechStartedHandler.accept(speechStartedHandlerEvent); + } + return; + } + } + if (node.has("speech_id") && "Flushed".equals(node.path("type").asText())) { + SpeakV2Flushed flushedHandlerEvent = null; + try { + flushedHandlerEvent = objectMapper.treeToValue(node, SpeakV2Flushed.class); + } catch (Exception e) { + } + if (flushedHandlerEvent != null) { + if (flushedHandler != null) { + flushedHandler.accept(flushedHandlerEvent); + } + return; + } + } + if (node.has("applied") + && "ConfigureSuccess".equals(node.path("type").asText())) { + SpeakV2ConfigureSuccess configureSuccessHandlerEvent = null; + try { + configureSuccessHandlerEvent = objectMapper.treeToValue(node, SpeakV2ConfigureSuccess.class); + } catch (Exception e) { + } + if (configureSuccessHandlerEvent != null) { + if (configureSuccessHandler != null) { + configureSuccessHandler.accept(configureSuccessHandlerEvent); + } + return; + } + } + // Unrecognized message type: forward-compatible no-op. The raw frame was + // already delivered to onMessage(String) above, so a newer server adding a + // benign control frame (e.g. a GA addition to this endpoint) must not surface + // as a fatal error to deployed clients. Routing it to onError would make a + // harmless frame look fatal to a voice agent; mirrors the JS/Python SDKs. + } catch (Exception e) { + if (onErrorHandler != null) { + onErrorHandler.accept(e); + } + } + } +} From b6018afe7e6f7d098a1b029e69d3319f0406794a Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 09:26:16 +0000 Subject: [PATCH 03/12] SDK regeneration --- .fern/metadata.json | 8 +- .../java/com/deepgram/core/ClientOptions.java | 9 +- .../core/ReconnectingWebSocketListener.java | 93 +-- .../agent/v1/types/AgentV1AgentAudioDone.java | 8 - .../agent/v1/types/AgentV1KeepAlive.java | 8 - .../agent/v1/types/AgentV1ListenUpdated.java | 8 - .../agent/v1/types/AgentV1PromptUpdated.java | 8 - ...ContextContextMessagesItemContentRole.java | 87 -- ...gesItemFunctionCallsFunctionCallsItem.java | 314 ------- ...tV1SettingsAgentContextListenProvider.java | 7 +- .../AgentV1SettingsAgentListenProvider.java | 7 +- .../v1/types/AgentV1SettingsApplied.java | 8 - .../agent/v1/types/AgentV1SpeakUpdated.java | 8 - .../agent/v1/types/AgentV1ThinkUpdated.java | 8 - .../AgentV1UpdateListenListenProvider.java | 7 +- .../v1/types/AgentV1UserStartedSpeaking.java | 8 - .../v1/types/ConversationHistoryMessage.java | 14 +- ...va => ConversationHistoryMessageRole.java} | 18 +- .../v1/types/FunctionCallHistoryMessage.java | 19 +- ...nCallHistoryMessageFunctionCallsItem.java} | 22 +- ...sultsChannelAlternativesItemWordsItem.java | 8 +- .../v1/types/ListenV1ResultsMetadata.java | 51 +- .../ListenV1ResultsMetadataDiarizeInfo.java | 162 ++++ .../v1/websocket/V1WebSocketClient.java | 39 +- .../listen/v2/types/ListenV2CloseStream.java | 8 - .../listen/v2/types/ListenV2ForceEndTurn.java | 78 ++ .../listen/v2/types/ListenV2TurnInfo.java | 93 +++ .../v2/websocket/V2WebSocketClient.java | 47 +- .../speak/v1/websocket/V1WebSocketClient.java | 12 - .../v2/audio/requests/SpeakV2Request.java | 8 +- .../speak/v2/types/SpeakV2Close.java | 8 - .../speak/v2/types/SpeakV2Flush.java | 8 - .../speak/v2/types/SpeakV2Warning.java | 6 +- .../speak/v2/websocket/V2ConnectOptions.java | 17 +- .../speak/v2/websocket/V2WebSocketClient.java | 29 +- .../java/com/deepgram/types/Anthropic.java | 12 +- ...ProviderModel.java => AnthropicModel.java} | 19 +- .../java/com/deepgram/types/Cartesia.java | 24 +- ...viderModelId.java => CartesiaModelId.java} | 17 +- ...kProviderVoice.java => CartesiaVoice.java} | 20 +- .../java/com/deepgram/types/Deepgram.java | 26 +- ...kProviderModel.java => DeepgramModel.java} | 772 +++++++++++------- src/main/java/com/deepgram/types/Google.java | 28 +- ...inkProviderModel.java => GoogleModel.java} | 22 +- ...roviderVersion.java => GoogleVersion.java} | 20 +- src/main/java/com/deepgram/types/Groq.java | 12 +- ...soningMode.java => GroqReasoningMode.java} | 23 +- .../types/ListenV1ResponseMetadata.java | 45 + .../ListenV1ResponseMetadataDiarizeInfo.java | 162 ++++ ...ChannelsItemAlternativesItemWordsItem.java | 66 +- .../java/com/deepgram/types/SpeakV2Speed.java | 132 +++ 51 files changed, 1486 insertions(+), 1157 deletions(-) delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextContextMessagesItemContentRole.java delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem.java rename src/main/java/com/deepgram/resources/agent/v1/types/{AgentV1HistoryContentRole.java => ConversationHistoryMessageRole.java} (68%) rename src/main/java/com/deepgram/resources/agent/v1/types/{AgentV1HistoryFunctionCallsFunctionCallsItem.java => FunctionCallHistoryMessageFunctionCallsItem.java} (91%) create mode 100644 src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsMetadataDiarizeInfo.java create mode 100644 src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java rename src/main/java/com/deepgram/types/{AnthropicThinkProviderModel.java => AnthropicModel.java} (68%) rename src/main/java/com/deepgram/types/{CartesiaSpeakProviderModelId.java => CartesiaModelId.java} (68%) rename src/main/java/com/deepgram/types/{CartesiaSpeakProviderVoice.java => CartesiaVoice.java} (84%) rename src/main/java/com/deepgram/types/{DeepgramSpeakProviderModel.java => DeepgramModel.java} (50%) rename src/main/java/com/deepgram/types/{GoogleThinkProviderModel.java => GoogleModel.java} (66%) rename src/main/java/com/deepgram/types/{GoogleThinkProviderVersion.java => GoogleVersion.java} (67%) rename src/main/java/com/deepgram/types/{GroqThinkProviderReasoningMode.java => GroqReasoningMode.java} (65%) create mode 100644 src/main/java/com/deepgram/types/ListenV1ResponseMetadataDiarizeInfo.java create mode 100644 src/main/java/com/deepgram/types/SpeakV2Speed.java diff --git a/.fern/metadata.json b/.fern/metadata.json index 4c711ab..2ccda24 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,7 +1,7 @@ { - "cliVersion": "5.44.6", + "cliVersion": "5.95.1", "generatorName": "fernapi/fern-java-sdk", - "generatorVersion": "4.16.0", + "generatorVersion": "4.18.0", "generatorConfig": { "package-prefix": "com.deepgram", "base-api-exception-class-name": "DeepgramHttpException", @@ -12,8 +12,8 @@ "enable-wire-tests": true, "runtime-version": true }, - "originGitCommit": "03f06776bbb692c49f8d76c4230bca7f4bc4bca7", + "originGitCommit": "068de888501fcc3b792086aab45de975587b89e1", "originGitCommitIsDirty": true, "invokedBy": "manual", - "sdkVersion": "0.8.0" + "sdkVersion": "0.8.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 61be390..ff7ba29 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -50,10 +50,10 @@ private ClientOptions( this.headers.putAll(headers); this.headers.putAll(new HashMap() { { - put("User-Agent", "com.deepgram:deepgram-java-sdk/0.8.0"); // x-release-please-version + put("User-Agent", "com.deepgram.deepgram-java-sdk/" + getSdkVersion()); put("X-Fern-Language", "JAVA"); put("X-Fern-SDK-Name", "com.deepgram:deepgram-java-sdk"); - put("X-Fern-SDK-Version", "0.8.0"); // x-release-please-version + put("X-Fern-SDK-Version", getSdkVersion()); } }); this.headerSuppliers = headerSuppliers; @@ -82,6 +82,11 @@ public Map headers(RequestOptions requestOptions) { return values; } + private static String getSdkVersion() { + String version = ClientOptions.class.getPackage().getImplementationVersion(); + return version != null ? version : "0.8.1"; + } + public int timeout(RequestOptions requestOptions) { if (requestOptions == null) { return this.timeout; diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java index a533068..0ca455a 100644 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java @@ -25,15 +25,14 @@ * Provides production-ready resilience for WebSocket connections. */ public abstract class ReconnectingWebSocketListener extends WebSocketListener { - // Overridable options are held behind a single volatile reference (not five separate volatile - // fields) so {@link #applyOptionsOverride} swaps them atomically — a reader that snapshots the - // reference once sees a mutually-consistent set (e.g. getNextDelay() cannot observe a new min - // paired with an old max). Rewiring is used by {@code TransportWebSocketFactory} to honour - // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. - private volatile ReconnectOptions activeOptions; - - // maxEnqueuedMessages is fixed at construction (the queue is sized once) and intentionally not - // overridable, so it stays a separate final field rather than being read from activeOptions. + private final long minReconnectionDelayMs; + + private final long maxReconnectionDelayMs; + + private final double reconnectionDelayGrowFactor; + + private final int maxRetries; + private final int maxEnqueuedMessages; private final AtomicInteger retryCount = new AtomicInteger(0); @@ -62,42 +61,21 @@ public abstract class ReconnectingWebSocketListener extends WebSocketListener { */ public ReconnectingWebSocketListener( ReconnectingWebSocketListener.ReconnectOptions options, Supplier connectionSupplier) { - this.activeOptions = options; + this.minReconnectionDelayMs = options.minReconnectionDelayMs; + this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; + this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; + this.maxRetries = options.maxRetries; this.maxEnqueuedMessages = options.maxEnqueuedMessages; 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: the override is a single atomic write to a {@code volatile} reference, so a - * reader that snapshots {@link #activeOptions} once sees a mutually-consistent set of values - * (never a new min paired with an old max). 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.activeOptions = options; - } - /** * 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 @@ -108,26 +86,20 @@ public void connect() { if (!connectLock.compareAndSet(false, true)) { return; } - // Snapshot the overridable options once so the gate and timeout below use a consistent set. - ReconnectOptions opts = this.activeOptions; - // 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() > opts.maxRetries) { + if (retryCount.get() >= maxRetries) { connectLock.set(false); return; } try { CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); try { - webSocket = connectionFuture.get(opts.connectionTimeoutMs, MILLISECONDS); + webSocket = connectionFuture.get(4000, MILLISECONDS); } catch (TimeoutException e) { connectionFuture.cancel(true); TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + opts.connectionTimeoutMs - + " milliseconds" + new TimeoutException("WebSocket connection timeout after " + 4000 + " milliseconds" + (retryCount.get() > 0 - ? " (retry attempt #" + retryCount.get() + ")" + ? " (retry attempt #" + retryCount.get() : " (initial connection attempt)")); onWebSocketFailure(null, timeoutError, null); if (shouldReconnect.get()) { @@ -342,14 +314,11 @@ public void onClosed(WebSocket webSocket, int code, String reason) { * - 2+ = exponential backoff up to maxReconnectionDelayMs */ private long getNextDelay() { - // Single volatile read → a consistent (min, growFactor, max) snapshot even if - // applyOptionsOverride swaps the options concurrently. - ReconnectOptions opts = this.activeOptions; if (retryCount.get() == 1) { - return opts.minReconnectionDelayMs; + return minReconnectionDelayMs; } - long delay = (long) (opts.minReconnectionDelayMs * Math.pow(opts.reconnectionDelayGrowFactor, retryCount.get() - 1)); - return Math.min(delay, opts.maxReconnectionDelayMs); + long delay = (long) (minReconnectionDelayMs * Math.pow(reconnectionDelayGrowFactor, retryCount.get() - 1)); + return Math.min(delay, maxReconnectionDelayMs); } /** @@ -430,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() { @@ -456,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) { @@ -492,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. * @@ -510,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 @@ -535,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/types/AgentV1AgentAudioDone.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java index 41af522..71825ed 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1AgentAudioDone; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java index 083578b..7842266 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1KeepAlive; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java index 6018473..65878c9 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1ListenUpdated; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java index 6edec37..68d71cc 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1PromptUpdated; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextContextMessagesItemContentRole.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextContextMessagesItemContentRole.java deleted file mode 100644 index 4098bb0..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextContextMessagesItemContentRole.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.agent.v1.types; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public final class AgentV1SettingsAgentContextContextMessagesItemContentRole { - public static final AgentV1SettingsAgentContextContextMessagesItemContentRole USER = - new AgentV1SettingsAgentContextContextMessagesItemContentRole(Value.USER, "user"); - - public static final AgentV1SettingsAgentContextContextMessagesItemContentRole ASSISTANT = - new AgentV1SettingsAgentContextContextMessagesItemContentRole(Value.ASSISTANT, "assistant"); - - private final Value value; - - private final String string; - - AgentV1SettingsAgentContextContextMessagesItemContentRole(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 AgentV1SettingsAgentContextContextMessagesItemContentRole - && this.string.equals( - ((AgentV1SettingsAgentContextContextMessagesItemContentRole) other).string)); - } - - @java.lang.Override - public int hashCode() { - return this.string.hashCode(); - } - - public T visit(Visitor visitor) { - switch (value) { - case USER: - return visitor.visitUser(); - case ASSISTANT: - return visitor.visitAssistant(); - case UNKNOWN: - default: - return visitor.visitUnknown(string); - } - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static AgentV1SettingsAgentContextContextMessagesItemContentRole valueOf(String value) { - switch (value) { - case "user": - return USER; - case "assistant": - return ASSISTANT; - default: - return new AgentV1SettingsAgentContextContextMessagesItemContentRole(Value.UNKNOWN, value); - } - } - - public enum Value { - USER, - - ASSISTANT, - - UNKNOWN - } - - public interface Visitor { - T visitUser(); - - T visitAssistant(); - - T visitUnknown(String unknownType); - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem.java deleted file mode 100644 index d563b7e..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem.java +++ /dev/null @@ -1,314 +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.Map; -import java.util.Objects; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem.Builder.class) -public final class AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem { - private final String id; - - private final String name; - - private final boolean clientSide; - - private final String arguments; - - private final String response; - - private final Optional thoughtSignature; - - private final Map additionalProperties; - - private AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem( - String id, - String name, - boolean clientSide, - String arguments, - String response, - Optional thoughtSignature, - Map additionalProperties) { - this.id = id; - this.name = name; - this.clientSide = clientSide; - this.arguments = arguments; - this.response = response; - this.thoughtSignature = thoughtSignature; - this.additionalProperties = additionalProperties; - } - - /** - * @return Unique identifier for the function call - */ - @JsonProperty("id") - public String getId() { - return id; - } - - /** - * @return Name of the function called - */ - @JsonProperty("name") - public String getName() { - return name; - } - - /** - * @return Indicates if the call was client-side or server-side - */ - @JsonProperty("client_side") - public boolean getClientSide() { - return clientSide; - } - - /** - * @return Arguments passed to the function - */ - @JsonProperty("arguments") - public String getArguments() { - return arguments; - } - - /** - * @return Response from the function call - */ - @JsonProperty("response") - public String getResponse() { - return response; - } - - /** - * @return Some Gemini models require this as an additional function call identifier - */ - @JsonProperty("thought_signature") - public Optional getThoughtSignature() { - return thoughtSignature; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem - && equalTo((AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem other) { - return id.equals(other.id) - && name.equals(other.name) - && clientSide == other.clientSide - && arguments.equals(other.arguments) - && response.equals(other.response) - && thoughtSignature.equals(other.thoughtSignature); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.id, this.name, this.clientSide, this.arguments, this.response, this.thoughtSignature); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static IdStage builder() { - return new Builder(); - } - - public interface IdStage { - /** - *

Unique identifier for the function call

- */ - NameStage id(@NotNull String id); - - Builder from(AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem other); - } - - public interface NameStage { - /** - *

Name of the function called

- */ - ClientSideStage name(@NotNull String name); - } - - public interface ClientSideStage { - /** - *

Indicates if the call was client-side or server-side

- */ - ArgumentsStage clientSide(boolean clientSide); - } - - public interface ArgumentsStage { - /** - *

Arguments passed to the function

- */ - ResponseStage arguments(@NotNull String arguments); - } - - public interface ResponseStage { - /** - *

Response from the function call

- */ - _FinalStage response(@NotNull String response); - } - - public interface _FinalStage { - AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem build(); - - _FinalStage additionalProperty(String key, Object value); - - _FinalStage additionalProperties(Map additionalProperties); - - /** - *

Some Gemini models require this as an additional function call identifier

- */ - _FinalStage thoughtSignature(Optional thoughtSignature); - - _FinalStage thoughtSignature(String thoughtSignature); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder - implements IdStage, NameStage, ClientSideStage, ArgumentsStage, ResponseStage, _FinalStage { - private String id; - - private String name; - - private boolean clientSide; - - private String arguments; - - private String response; - - private Optional thoughtSignature = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - @java.lang.Override - public Builder from(AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem other) { - id(other.getId()); - name(other.getName()); - clientSide(other.getClientSide()); - arguments(other.getArguments()); - response(other.getResponse()); - thoughtSignature(other.getThoughtSignature()); - return this; - } - - /** - *

Unique identifier for the function call

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

Name of the function called

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

Indicates if the call was client-side or server-side

- * @return Reference to {@code this} so that method calls can be chained together. - */ - @java.lang.Override - @JsonSetter("client_side") - public ArgumentsStage clientSide(boolean clientSide) { - this.clientSide = clientSide; - return this; - } - - /** - *

Arguments passed to the function

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

Response from the function call

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

Some Gemini models require this as an additional function call identifier

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

Some Gemini models require this as an additional function call identifier

- */ - @java.lang.Override - @JsonSetter(value = "thought_signature", nulls = Nulls.SKIP) - public _FinalStage thoughtSignature(Optional thoughtSignature) { - this.thoughtSignature = thoughtSignature; - return this; - } - - @java.lang.Override - public AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem build() { - return new AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem( - id, name, clientSide, arguments, response, thoughtSignature, 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/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java index c4ee1de..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 @@ -99,12 +99,7 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an - // empty body, so a provider payload omitting the optional "version" discriminator (exactly what - // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes - // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop - // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java index 3bf0333..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 @@ -99,12 +99,7 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an - // empty body, so a provider payload omitting the optional "version" discriminator (exactly what - // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes - // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop - // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java index fb23c60..0d60b72 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1SettingsApplied; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java index 7619157..747aa9d 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1SpeakUpdated; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java index 2f2a981..5668a71 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1ThinkUpdated; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java index a0b84d9..e693a04 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java @@ -99,12 +99,7 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an - // empty body, so a provider payload omitting the optional "version" discriminator (exactly what - // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes - // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop - // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java index 7c3fc38..edb80f8 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof AgentV1UserStartedSpeaking; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/ConversationHistoryMessage.java b/src/main/java/com/deepgram/resources/agent/v1/types/ConversationHistoryMessage.java index 8417eeb..f5c4763 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/ConversationHistoryMessage.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/ConversationHistoryMessage.java @@ -19,16 +19,14 @@ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ConversationHistoryMessage.Builder.class) public final class ConversationHistoryMessage { - private final AgentV1SettingsAgentContextContextMessagesItemContentRole role; + private final ConversationHistoryMessageRole role; private final String content; private final Map additionalProperties; private ConversationHistoryMessage( - AgentV1SettingsAgentContextContextMessagesItemContentRole role, - String content, - Map additionalProperties) { + ConversationHistoryMessageRole role, String content, Map additionalProperties) { this.role = role; this.content = content; this.additionalProperties = additionalProperties; @@ -46,7 +44,7 @@ public String getType() { * @return Identifies who spoke the statement */ @JsonProperty("role") - public AgentV1SettingsAgentContextContextMessagesItemContentRole getRole() { + public ConversationHistoryMessageRole getRole() { return role; } @@ -91,7 +89,7 @@ public interface RoleStage { /** *

Identifies who spoke the statement

*/ - ContentStage role(@NotNull AgentV1SettingsAgentContextContextMessagesItemContentRole role); + ContentStage role(@NotNull ConversationHistoryMessageRole role); Builder from(ConversationHistoryMessage other); } @@ -113,7 +111,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements RoleStage, ContentStage, _FinalStage { - private AgentV1SettingsAgentContextContextMessagesItemContentRole role; + private ConversationHistoryMessageRole role; private String content; @@ -135,7 +133,7 @@ public Builder from(ConversationHistoryMessage other) { */ @java.lang.Override @JsonSetter("role") - public ContentStage role(@NotNull AgentV1SettingsAgentContextContextMessagesItemContentRole role) { + public ContentStage role(@NotNull ConversationHistoryMessageRole role) { this.role = Objects.requireNonNull(role, "role must not be null"); return this; } diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1HistoryContentRole.java b/src/main/java/com/deepgram/resources/agent/v1/types/ConversationHistoryMessageRole.java similarity index 68% rename from src/main/java/com/deepgram/resources/agent/v1/types/AgentV1HistoryContentRole.java rename to src/main/java/com/deepgram/resources/agent/v1/types/ConversationHistoryMessageRole.java index e722ce3..1ce3f01 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1HistoryContentRole.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/ConversationHistoryMessageRole.java @@ -6,17 +6,17 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -public final class AgentV1HistoryContentRole { - public static final AgentV1HistoryContentRole USER = new AgentV1HistoryContentRole(Value.USER, "user"); +public final class ConversationHistoryMessageRole { + public static final ConversationHistoryMessageRole USER = new ConversationHistoryMessageRole(Value.USER, "user"); - public static final AgentV1HistoryContentRole ASSISTANT = - new AgentV1HistoryContentRole(Value.ASSISTANT, "assistant"); + public static final ConversationHistoryMessageRole ASSISTANT = + new ConversationHistoryMessageRole(Value.ASSISTANT, "assistant"); private final Value value; private final String string; - AgentV1HistoryContentRole(Value value, String string) { + ConversationHistoryMessageRole(Value value, String string) { this.value = value; this.string = string; } @@ -34,8 +34,8 @@ public String toString() { @java.lang.Override public boolean equals(Object other) { return (this == other) - || (other instanceof AgentV1HistoryContentRole - && this.string.equals(((AgentV1HistoryContentRole) other).string)); + || (other instanceof ConversationHistoryMessageRole + && this.string.equals(((ConversationHistoryMessageRole) other).string)); } @java.lang.Override @@ -56,14 +56,14 @@ public T visit(Visitor visitor) { } @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static AgentV1HistoryContentRole valueOf(String value) { + public static ConversationHistoryMessageRole valueOf(String value) { switch (value) { case "user": return USER; case "assistant": return ASSISTANT; default: - return new AgentV1HistoryContentRole(Value.UNKNOWN, value); + return new ConversationHistoryMessageRole(Value.UNKNOWN, value); } } diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/FunctionCallHistoryMessage.java b/src/main/java/com/deepgram/resources/agent/v1/types/FunctionCallHistoryMessage.java index 9fa665e..c60635b 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/FunctionCallHistoryMessage.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/FunctionCallHistoryMessage.java @@ -21,13 +21,12 @@ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FunctionCallHistoryMessage.Builder.class) public final class FunctionCallHistoryMessage { - private final List functionCalls; + private final List functionCalls; private final Map additionalProperties; private FunctionCallHistoryMessage( - List functionCalls, - Map additionalProperties) { + List functionCalls, Map additionalProperties) { this.functionCalls = functionCalls; this.additionalProperties = additionalProperties; } @@ -41,7 +40,7 @@ public String getType() { * @return List of function call objects */ @JsonProperty("function_calls") - public List getFunctionCalls() { + public List getFunctionCalls() { return functionCalls; } @@ -76,8 +75,7 @@ public static Builder builder() { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder { - private List functionCalls = - new ArrayList<>(); + private List functionCalls = new ArrayList<>(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -93,8 +91,7 @@ public Builder from(FunctionCallHistoryMessage other) { *

List of function call objects

*/ @JsonSetter(value = "function_calls", nulls = Nulls.SKIP) - public Builder functionCalls( - List functionCalls) { + public Builder functionCalls(List functionCalls) { this.functionCalls.clear(); if (functionCalls != null) { this.functionCalls.addAll(functionCalls); @@ -102,14 +99,12 @@ public Builder functionCalls( return this; } - public Builder addFunctionCalls( - AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem functionCalls) { + public Builder addFunctionCalls(FunctionCallHistoryMessageFunctionCallsItem functionCalls) { this.functionCalls.add(functionCalls); return this; } - public Builder addAllFunctionCalls( - List functionCalls) { + public Builder addAllFunctionCalls(List functionCalls) { if (functionCalls != null) { this.functionCalls.addAll(functionCalls); } diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1HistoryFunctionCallsFunctionCallsItem.java b/src/main/java/com/deepgram/resources/agent/v1/types/FunctionCallHistoryMessageFunctionCallsItem.java similarity index 91% rename from src/main/java/com/deepgram/resources/agent/v1/types/AgentV1HistoryFunctionCallsFunctionCallsItem.java rename to src/main/java/com/deepgram/resources/agent/v1/types/FunctionCallHistoryMessageFunctionCallsItem.java index 4a45de4..a9a9f6c 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1HistoryFunctionCallsFunctionCallsItem.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/FunctionCallHistoryMessageFunctionCallsItem.java @@ -19,8 +19,8 @@ import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1HistoryFunctionCallsFunctionCallsItem.Builder.class) -public final class AgentV1HistoryFunctionCallsFunctionCallsItem { +@JsonDeserialize(builder = FunctionCallHistoryMessageFunctionCallsItem.Builder.class) +public final class FunctionCallHistoryMessageFunctionCallsItem { private final String id; private final String name; @@ -35,7 +35,7 @@ public final class AgentV1HistoryFunctionCallsFunctionCallsItem { private final Map additionalProperties; - private AgentV1HistoryFunctionCallsFunctionCallsItem( + private FunctionCallHistoryMessageFunctionCallsItem( String id, String name, boolean clientSide, @@ -103,8 +103,8 @@ public Optional getThoughtSignature() { @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof AgentV1HistoryFunctionCallsFunctionCallsItem - && equalTo((AgentV1HistoryFunctionCallsFunctionCallsItem) other); + return other instanceof FunctionCallHistoryMessageFunctionCallsItem + && equalTo((FunctionCallHistoryMessageFunctionCallsItem) other); } @JsonAnyGetter @@ -112,7 +112,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(AgentV1HistoryFunctionCallsFunctionCallsItem other) { + private boolean equalTo(FunctionCallHistoryMessageFunctionCallsItem other) { return id.equals(other.id) && name.equals(other.name) && clientSide == other.clientSide @@ -141,7 +141,7 @@ public interface IdStage { */ NameStage id(@NotNull String id); - Builder from(AgentV1HistoryFunctionCallsFunctionCallsItem other); + Builder from(FunctionCallHistoryMessageFunctionCallsItem other); } public interface NameStage { @@ -173,7 +173,7 @@ public interface ResponseStage { } public interface _FinalStage { - AgentV1HistoryFunctionCallsFunctionCallsItem build(); + FunctionCallHistoryMessageFunctionCallsItem build(); _FinalStage additionalProperty(String key, Object value); @@ -208,7 +208,7 @@ public static final class Builder private Builder() {} @java.lang.Override - public Builder from(AgentV1HistoryFunctionCallsFunctionCallsItem other) { + public Builder from(FunctionCallHistoryMessageFunctionCallsItem other) { id(other.getId()); name(other.getName()); clientSide(other.getClientSide()); @@ -294,8 +294,8 @@ public _FinalStage thoughtSignature(Optional thoughtSignature) { } @java.lang.Override - public AgentV1HistoryFunctionCallsFunctionCallsItem build() { - return new AgentV1HistoryFunctionCallsFunctionCallsItem( + public FunctionCallHistoryMessageFunctionCallsItem build() { + return new FunctionCallHistoryMessageFunctionCallsItem( id, name, clientSide, arguments, response, thoughtSignature, additionalProperties); } diff --git a/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsChannelAlternativesItemWordsItem.java b/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsChannelAlternativesItemWordsItem.java index c07c561..e84e7e7 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsChannelAlternativesItemWordsItem.java +++ b/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsChannelAlternativesItemWordsItem.java @@ -105,7 +105,7 @@ public Optional getPunctuatedWord() { } /** - * @return The speaker of the word + * @return The speaker of the word, present when diarization is enabled */ @JsonProperty("speaker") public Optional getSpeaker() { @@ -201,7 +201,7 @@ public interface _FinalStage { _FinalStage punctuatedWord(String punctuatedWord); /** - *

The speaker of the word

+ *

The speaker of the word, present when diarization is enabled

*/ _FinalStage speaker(Optional speaker); @@ -286,7 +286,7 @@ public _FinalStage confidence(double confidence) { } /** - *

The speaker of the word

+ *

The speaker of the word, present when diarization is enabled

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

The speaker of the word

+ *

The speaker of the word, present when diarization is enabled

*/ @java.lang.Override @JsonSetter(value = "speaker", nulls = Nulls.SKIP) diff --git a/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsMetadata.java b/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsMetadata.java index a7f01b5..d49fbff 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsMetadata.java +++ b/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsMetadata.java @@ -10,10 +10,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @@ -25,16 +27,20 @@ public final class ListenV1ResultsMetadata { private final String modelUuid; + private final Optional diarizeInfo; + private final Map additionalProperties; private ListenV1ResultsMetadata( String requestId, ListenV1ResultsMetadataModelInfo modelInfo, String modelUuid, + Optional diarizeInfo, Map additionalProperties) { this.requestId = requestId; this.modelInfo = modelInfo; this.modelUuid = modelUuid; + this.diarizeInfo = diarizeInfo; this.additionalProperties = additionalProperties; } @@ -59,6 +65,14 @@ public String getModelUuid() { return modelUuid; } + /** + * @return The diarizer that produced the speaker labels. Present only when a diarizer ran. + */ + @JsonProperty("diarize_info") + public Optional getDiarizeInfo() { + return diarizeInfo; + } + @java.lang.Override public boolean equals(Object other) { if (this == other) return true; @@ -73,12 +87,13 @@ public Map getAdditionalProperties() { private boolean equalTo(ListenV1ResultsMetadata other) { return requestId.equals(other.requestId) && modelInfo.equals(other.modelInfo) - && modelUuid.equals(other.modelUuid); + && modelUuid.equals(other.modelUuid) + && diarizeInfo.equals(other.diarizeInfo); } @java.lang.Override public int hashCode() { - return Objects.hash(this.requestId, this.modelInfo, this.modelUuid); + return Objects.hash(this.requestId, this.modelInfo, this.modelUuid, this.diarizeInfo); } @java.lang.Override @@ -116,6 +131,13 @@ public interface _FinalStage { _FinalStage additionalProperty(String key, Object value); _FinalStage additionalProperties(Map additionalProperties); + + /** + *

The diarizer that produced the speaker labels. Present only when a diarizer ran.

+ */ + _FinalStage diarizeInfo(Optional diarizeInfo); + + _FinalStage diarizeInfo(ListenV1ResultsMetadataDiarizeInfo diarizeInfo); } @JsonIgnoreProperties(ignoreUnknown = true) @@ -126,6 +148,8 @@ public static final class Builder implements RequestIdStage, ModelInfoStage, Mod private String modelUuid; + private Optional diarizeInfo = Optional.empty(); + @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -136,6 +160,7 @@ public Builder from(ListenV1ResultsMetadata other) { requestId(other.getRequestId()); modelInfo(other.getModelInfo()); modelUuid(other.getModelUuid()); + diarizeInfo(other.getDiarizeInfo()); return this; } @@ -168,9 +193,29 @@ public _FinalStage modelUuid(@NotNull String modelUuid) { return this; } + /** + *

The diarizer that produced the speaker labels. Present only when a diarizer ran.

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

The diarizer that produced the speaker labels. Present only when a diarizer ran.

+ */ + @java.lang.Override + @JsonSetter(value = "diarize_info", nulls = Nulls.SKIP) + public _FinalStage diarizeInfo(Optional diarizeInfo) { + this.diarizeInfo = diarizeInfo; + return this; + } + @java.lang.Override public ListenV1ResultsMetadata build() { - return new ListenV1ResultsMetadata(requestId, modelInfo, modelUuid, additionalProperties); + return new ListenV1ResultsMetadata(requestId, modelInfo, modelUuid, diarizeInfo, additionalProperties); } @java.lang.Override diff --git a/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsMetadataDiarizeInfo.java b/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsMetadataDiarizeInfo.java new file mode 100644 index 0000000..ddb1739 --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v1/types/ListenV1ResultsMetadataDiarizeInfo.java @@ -0,0 +1,162 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ListenV1ResultsMetadataDiarizeInfo.Builder.class) +public final class ListenV1ResultsMetadataDiarizeInfo { + private final String modelUuid; + + private final String arch; + + private final Map additionalProperties; + + private ListenV1ResultsMetadataDiarizeInfo( + String modelUuid, String arch, Map additionalProperties) { + this.modelUuid = modelUuid; + this.arch = arch; + this.additionalProperties = additionalProperties; + } + + /** + * @return The diarizer model UUID + */ + @JsonProperty("model_uuid") + public String getModelUuid() { + return modelUuid; + } + + /** + * @return The diarizer arch, such as v1 or v2 + */ + @JsonProperty("arch") + public String getArch() { + return arch; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ListenV1ResultsMetadataDiarizeInfo + && equalTo((ListenV1ResultsMetadataDiarizeInfo) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ListenV1ResultsMetadataDiarizeInfo other) { + return modelUuid.equals(other.modelUuid) && arch.equals(other.arch); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.modelUuid, this.arch); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static ModelUuidStage builder() { + return new Builder(); + } + + public interface ModelUuidStage { + /** + *

The diarizer model UUID

+ */ + ArchStage modelUuid(@NotNull String modelUuid); + + Builder from(ListenV1ResultsMetadataDiarizeInfo other); + } + + public interface ArchStage { + /** + *

The diarizer arch, such as v1 or v2

+ */ + _FinalStage arch(@NotNull String arch); + } + + public interface _FinalStage { + ListenV1ResultsMetadataDiarizeInfo build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements ModelUuidStage, ArchStage, _FinalStage { + private String modelUuid; + + private String arch; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(ListenV1ResultsMetadataDiarizeInfo other) { + modelUuid(other.getModelUuid()); + arch(other.getArch()); + return this; + } + + /** + *

The diarizer model UUID

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

The diarizer arch, such as v1 or v2

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("arch") + public _FinalStage arch(@NotNull String arch) { + this.arch = Objects.requireNonNull(arch, "arch must not be null"); + return this; + } + + @java.lang.Override + public ListenV1ResultsMetadataDiarizeInfo build() { + return new ListenV1ResultsMetadataDiarizeInfo(modelUuid, arch, 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/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java index ffa4953..4952808 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java @@ -6,7 +6,6 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -139,12 +138,8 @@ public CompletableFuture connect(V1ConnectOptions options) { "endpointing", String.valueOf(options.getEndpointing().get())); } if (options.getExtra() != null && options.getExtra().isPresent()) { - // Array-valued query params (String | List unions) must serialize as repeated - // params (extra=a&extra=b), not a stringified list. The generated streaming template - // uses String.valueOf(...), which mangles a List into "[a, b]"; route these through - // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. - QueryStringMapper.addQueryParameter( - urlBuilder, "extra", options.getExtra().get().get(), true); + urlBuilder.addQueryParameter( + "extra", String.valueOf(options.getExtra().get())); } if (options.getInterimResults() != null && options.getInterimResults().isPresent()) { urlBuilder.addQueryParameter( @@ -152,12 +147,12 @@ public CompletableFuture connect(V1ConnectOptions options) { String.valueOf(options.getInterimResults().get())); } if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "keyterm", options.getKeyterm().get().get(), true); + urlBuilder.addQueryParameter( + "keyterm", String.valueOf(options.getKeyterm().get())); } if (options.getKeywords() != null && options.getKeywords().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "keywords", options.getKeywords().get().get(), true); + urlBuilder.addQueryParameter( + "keywords", String.valueOf(options.getKeywords().get())); } if (options.getLanguage() != null && options.getLanguage().isPresent()) { urlBuilder.addQueryParameter( @@ -190,24 +185,23 @@ public CompletableFuture connect(V1ConnectOptions options) { "redact", String.valueOf(options.getRedact().get())); } if (options.getReplace() != null && options.getReplace().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "replace", options.getReplace().get().get(), true); + urlBuilder.addQueryParameter( + "replace", String.valueOf(options.getReplace().get())); } if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { urlBuilder.addQueryParameter( "sample_rate", String.valueOf(options.getSampleRate().get())); } if (options.getSearch() != null && options.getSearch().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "search", options.getSearch().get().get(), true); + urlBuilder.addQueryParameter( + "search", String.valueOf(options.getSearch().get())); } if (options.getSmartFormat() != null && options.getSmartFormat().isPresent()) { urlBuilder.addQueryParameter( "smart_format", String.valueOf(options.getSmartFormat().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "tag", options.getTag().get().get(), true); + urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); } if (options.getUtteranceEndMs() != null && options.getUtteranceEndMs().isPresent()) { urlBuilder.addQueryParameter( @@ -222,17 +216,6 @@ public CompletableFuture connect(V1ConnectOptions options) { urlBuilder.addQueryParameter( "version", String.valueOf(options.getVersion().get())); } - // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. - // The generated template only serializes the typed options and drops these otherwise. - // ConnectOptions is request-only (never deserialized), so this map holds only what the - // caller set via the builder. Routed through QueryStringMapper to match the REST path. - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java index e508681..e6308e2 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof ListenV2CloseStream; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java new file mode 100644 index 0000000..c3dbd38 --- /dev/null +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java @@ -0,0 +1,78 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.resources.listen.v2.types; + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ListenV2ForceEndTurn.Builder.class) +public final class ListenV2ForceEndTurn { + private final Map additionalProperties; + + private ListenV2ForceEndTurn(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + /** + * @return Message type identifier + */ + @JsonProperty("type") + public String getType() { + return "ForceEndTurn"; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ListenV2ForceEndTurn; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ListenV2ForceEndTurn other) { + return this; + } + + public ListenV2ForceEndTurn build() { + return new ListenV2ForceEndTurn(additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfo.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfo.java index 9e1d149..9eab9fe 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfo.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2TurnInfo.java @@ -41,6 +41,8 @@ public final class ListenV2TurnInfo { private final float endOfTurnConfidence; + private final Optional trigger; + private final Optional> languages; private final Optional> languagesHinted; @@ -57,6 +59,7 @@ private ListenV2TurnInfo( String transcript, List words, float endOfTurnConfidence, + Optional trigger, Optional> languages, Optional> languagesHinted, Map additionalProperties) { @@ -69,6 +72,7 @@ private ListenV2TurnInfo( this.transcript = transcript; this.words = words; this.endOfTurnConfidence = endOfTurnConfidence; + this.trigger = trigger; this.languages = languages; this.languagesHinted = languagesHinted; this.additionalProperties = additionalProperties; @@ -158,6 +162,26 @@ public float getEndOfTurnConfidence() { return endOfTurnConfidence; } + /** + * @return The cause of the turn ending. Present on every EndOfTurn event and only there. + *
    + *
  • + *

    model - the turn ended by Flux's native end-of-turn detection

    + *
  • + *
  • + *

    manual - the turn ended because a ForceEndTurn message was sent

    + *
  • + *
  • + *

    timeout - the turn ended because eot_timeout_ms elapsed

    + *
  • + *
+ *

This is an open enum. New values may be added over time, so clients must tolerate values they do not recognize.

+ */ + @JsonProperty("trigger") + public Optional getTrigger() { + return trigger; + } + /** * @return Detected languages sorted by descending frequency in the * transcript. Only present when the flux-general-multi model @@ -198,6 +222,7 @@ private boolean equalTo(ListenV2TurnInfo other) { && transcript.equals(other.transcript) && words.equals(other.words) && endOfTurnConfidence == other.endOfTurnConfidence + && trigger.equals(other.trigger) && languages.equals(other.languages) && languagesHinted.equals(other.languagesHinted); } @@ -214,6 +239,7 @@ public int hashCode() { this.transcript, this.words, this.endOfTurnConfidence, + this.trigger, this.languages, this.languagesHinted); } @@ -308,6 +334,25 @@ public interface _FinalStage { _FinalStage addAllWords(List words); + /** + *

The cause of the turn ending. Present on every EndOfTurn event and only there.

+ *
    + *
  • + *

    model - the turn ended by Flux's native end-of-turn detection

    + *
  • + *
  • + *

    manual - the turn ended because a ForceEndTurn message was sent

    + *
  • + *
  • + *

    timeout - the turn ended because eot_timeout_ms elapsed

    + *
  • + *
+ *

This is an open enum. New values may be added over time, so clients must tolerate values they do not recognize.

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

Detected languages sorted by descending frequency in the * transcript. Only present when the flux-general-multi model @@ -357,6 +402,8 @@ public static final class Builder private Optional> languages = Optional.empty(); + private Optional trigger = Optional.empty(); + private List words = new ArrayList<>(); @JsonAnySetter @@ -375,6 +422,7 @@ public Builder from(ListenV2TurnInfo other) { transcript(other.getTranscript()); words(other.getWords()); endOfTurnConfidence(other.getEndOfTurnConfidence()); + trigger(other.getTrigger()); languages(other.getLanguages()); languagesHinted(other.getLanguagesHinted()); return this; @@ -521,6 +569,50 @@ public _FinalStage languages(Optional> languages) { return this; } + /** + *

The cause of the turn ending. Present on every EndOfTurn event and only there.

+ *
    + *
  • + *

    model - the turn ended by Flux's native end-of-turn detection

    + *
  • + *
  • + *

    manual - the turn ended because a ForceEndTurn message was sent

    + *
  • + *
  • + *

    timeout - the turn ended because eot_timeout_ms elapsed

    + *
  • + *
+ *

This is an open enum. New values may be added over time, so clients must tolerate values they do not recognize.

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

The cause of the turn ending. Present on every EndOfTurn event and only there.

+ *
    + *
  • + *

    model - the turn ended by Flux's native end-of-turn detection

    + *
  • + *
  • + *

    manual - the turn ended because a ForceEndTurn message was sent

    + *
  • + *
  • + *

    timeout - the turn ended because eot_timeout_ms elapsed

    + *
  • + *
+ *

This is an open enum. New values may be added over time, so clients must tolerate values they do not recognize.

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

The words in the transcript

* @return Reference to {@code this} so that method calls can be chained together. @@ -568,6 +660,7 @@ public ListenV2TurnInfo build() { transcript, words, endOfTurnConfidence, + trigger, languages, languagesHinted, additionalProperties); diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java index 63f44e4..0e1535b 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java @@ -6,7 +6,6 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -16,6 +15,7 @@ import com.deepgram.resources.listen.v2.types.ListenV2ConfigureSuccess; import com.deepgram.resources.listen.v2.types.ListenV2Connected; import com.deepgram.resources.listen.v2.types.ListenV2FatalError; +import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; import com.deepgram.resources.listen.v2.types.ListenV2TurnInfo; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -126,16 +126,12 @@ public CompletableFuture connect(V2ConnectOptions options) { "eot_timeout_ms", String.valueOf(options.getEotTimeoutMs().get())); } if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - // Array-valued query params (String | List unions) must serialize as repeated - // params (keyterm=a&keyterm=b), not a stringified list. The generated streaming template - // uses String.valueOf(...), which mangles a List into "[a, b]"; route these through - // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. - QueryStringMapper.addQueryParameter( - urlBuilder, "keyterm", options.getKeyterm().get().get(), true); + urlBuilder.addQueryParameter( + "keyterm", String.valueOf(options.getKeyterm().get())); } if (options.getLanguageHint() != null && options.getLanguageHint().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "language_hint", options.getLanguageHint().get().get(), true); + urlBuilder.addQueryParameter( + "language_hint", String.valueOf(options.getLanguageHint().get())); } if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { urlBuilder.addQueryParameter( @@ -155,19 +151,7 @@ public CompletableFuture connect(V2ConnectOptions options) { "mip_opt_out", String.valueOf(options.getMipOptOut().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "tag", options.getTag().get().get(), true); - } - // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. - // The generated template only serializes the typed options and drops these otherwise. - // ConnectOptions is request-only (never deserialized), so this map holds only what the - // caller set via the builder. Routed through QueryStringMapper to match the REST path. - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); + urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); @@ -271,6 +255,15 @@ public CompletableFuture sendCloseStream(ListenV2CloseStream message) { return sendMessage(message); } + /** + * Sends a ListenV2ForceEndTurn message to the server asynchronously. + * @param message the message to send + * @return a CompletableFuture that completes when the message is sent + */ + public CompletableFuture sendForceEndTurn(ListenV2ForceEndTurn message) { + return sendMessage(message); + } + /** * Sends a ListenV2Configure message to the server asynchronously. * @param message the message to send @@ -494,11 +487,11 @@ private void handleIncomingMessage(String json) { return; } } - // Unrecognized message type: forward-compatible no-op. The raw frame was - // already delivered to onMessage(String) above, so a newer server adding a - // benign control frame (e.g. a GA addition to this endpoint) must not surface - // as a fatal error to deployed clients. Routing it to onError would make a - // harmless frame look fatal; mirrors the speak v2 client and the JS/Python SDKs. + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException( + "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) + + "... Update your SDK version to support new message types.")); + } } catch (Exception e) { if (onErrorHandler != null) { onErrorHandler.accept(e); diff --git a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java index 8033a55..8af4275 100644 --- a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java @@ -6,7 +6,6 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -123,17 +122,6 @@ public CompletableFuture connect(V1ConnectOptions options) { urlBuilder.addQueryParameter( "speed", String.valueOf(options.getSpeed().get())); } - // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. - // The generated template only serializes the typed options and drops these otherwise. - // ConnectOptions is request-only (never deserialized), so this map holds only what the - // caller set via the builder. Routed through QueryStringMapper to match the REST path. - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); diff --git a/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java b/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java index 2c7dc8b..d15c1fe 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java +++ b/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java @@ -143,7 +143,7 @@ public Optional getEncoding() { } /** - * @return Expressive range of the generated speech. Accepted values: -2, -1, 0, 1, 2. 0 is the voice's nominal delivery; negative values are flatter and more restrained, positive values more animated. + * @return Expressive range of the generated speech, on a calm-to-animated axis. Accepted values: -2, -1, 0, 1, 2. 0 (the default) is the voice's tuned delivery and the production-validated setting, with -2 the calm end of the range and 2 the animated end. Supported on all Flux voices; applies to the whole request. Beta: behavior may change in future model versions, and non-default values increase the risk of hallucinations and pronunciation errors; audition before shipping. An invalid value is rejected with a 400EXPRESSIVITY_OUT_OF_RANGE for a value outside the range, EXPRESSIVITY_INCREMENT_INVALID for a fractional value. See Expressivity. */ @JsonIgnore public Optional getExpressivity() { @@ -319,7 +319,7 @@ public interface _FinalStage { _FinalStage encoding(AudioGenerateRequestEncoding encoding); /** - *

Expressive range of the generated speech. Accepted values: -2, -1, 0, 1, 2. 0 is the voice's nominal delivery; negative values are flatter and more restrained, positive values more animated.

+ *

Expressive range of the generated speech, on a calm-to-animated axis. Accepted values: -2, -1, 0, 1, 2. 0 (the default) is the voice's tuned delivery and the production-validated setting, with -2 the calm end of the range and 2 the animated end. Supported on all Flux voices; applies to the whole request. Beta: behavior may change in future model versions, and non-default values increase the risk of hallucinations and pronunciation errors; audition before shipping. An invalid value is rejected with a 400EXPRESSIVITY_OUT_OF_RANGE for a value outside the range, EXPRESSIVITY_INCREMENT_INVALID for a fractional value. See Expressivity.

*/ _FinalStage expressivity(Optional expressivity); @@ -481,7 +481,7 @@ public _FinalStage sampleRate(Optional sampleRate) { } /** - *

Expressive range of the generated speech. Accepted values: -2, -1, 0, 1, 2. 0 is the voice's nominal delivery; negative values are flatter and more restrained, positive values more animated.

+ *

Expressive range of the generated speech, on a calm-to-animated axis. Accepted values: -2, -1, 0, 1, 2. 0 (the default) is the voice's tuned delivery and the production-validated setting, with -2 the calm end of the range and 2 the animated end. Supported on all Flux voices; applies to the whole request. Beta: behavior may change in future model versions, and non-default values increase the risk of hallucinations and pronunciation errors; audition before shipping. An invalid value is rejected with a 400EXPRESSIVITY_OUT_OF_RANGE for a value outside the range, EXPRESSIVITY_INCREMENT_INVALID for a fractional value. See Expressivity.

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

Expressive range of the generated speech. Accepted values: -2, -1, 0, 1, 2. 0 is the voice's nominal delivery; negative values are flatter and more restrained, positive values more animated.

+ *

Expressive range of the generated speech, on a calm-to-animated axis. Accepted values: -2, -1, 0, 1, 2. 0 (the default) is the voice's tuned delivery and the production-validated setting, with -2 the calm end of the range and 2 the animated end. Supported on all Flux voices; applies to the whole request. Beta: behavior may change in future model versions, and non-default values increase the risk of hallucinations and pronunciation errors; audition before shipping. An invalid value is rejected with a 400EXPRESSIVITY_OUT_OF_RANGE for a value outside the range, EXPRESSIVITY_INCREMENT_INVALID for a fractional value. See Expressivity.

*/ @java.lang.Override @JsonSetter(value = "expressivity", nulls = Nulls.SKIP) diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java index 51fef19..2e96a5c 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof SpeakV2Close; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java index e122400..acd717d 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java @@ -36,14 +36,6 @@ public boolean equals(Object other) { return other instanceof SpeakV2Flush; } - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Warning.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Warning.java index 0e44840..0eb0b3c 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Warning.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Warning.java @@ -41,7 +41,7 @@ public String getType() { /** * @return Warning code identifying the condition, in SCREAMING_SNAKE_CASE. - *

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

+ *

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

*

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

*

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

*/ @@ -90,7 +90,7 @@ public static CodeStage builder() { public interface CodeStage { /** *

Warning code identifying the condition, in SCREAMING_SNAKE_CASE.

- *

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

+ *

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

*

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

*

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

*/ @@ -134,7 +134,7 @@ public Builder from(SpeakV2Warning other) { /** *

Warning code identifying the condition, in SCREAMING_SNAKE_CASE.

- *

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

+ *

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

*

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

*

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

* @return Reference to {@code this} so that method calls can be chained together. diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java index 1173c8a..ca66de0 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java +++ b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java @@ -7,6 +7,7 @@ import com.deepgram.types.SpeakV2Encoding; import com.deepgram.types.SpeakV2MipOptOut; import com.deepgram.types.SpeakV2SampleRate; +import com.deepgram.types.SpeakV2Speed; import com.deepgram.types.SpeakV2Tag; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -31,7 +32,7 @@ public final class V2ConnectOptions { private final Optional sampleRate; - private final Optional speed; + private final Optional speed; private final Optional expressivity; @@ -45,7 +46,7 @@ private V2ConnectOptions( String model, Optional encoding, Optional sampleRate, - Optional speed, + Optional speed, Optional expressivity, Optional mipOptOut, Optional tag, @@ -76,7 +77,7 @@ public Optional getSampleRate() { } @JsonProperty("speed") - public Optional getSpeed() { + public Optional getSpeed() { return speed; } @@ -152,9 +153,9 @@ public interface _FinalStage { _FinalStage sampleRate(SpeakV2SampleRate sampleRate); - _FinalStage speed(Optional speed); + _FinalStage speed(Optional speed); - _FinalStage speed(Double speed); + _FinalStage speed(SpeakV2Speed speed); _FinalStage expressivity(Optional expressivity); @@ -179,7 +180,7 @@ public static final class Builder implements ModelStage, _FinalStage { private Optional expressivity = Optional.empty(); - private Optional speed = Optional.empty(); + private Optional speed = Optional.empty(); private Optional sampleRate = Optional.empty(); @@ -249,14 +250,14 @@ public _FinalStage expressivity(Optional expressivity) { } @java.lang.Override - public _FinalStage speed(Double speed) { + public _FinalStage speed(SpeakV2Speed speed) { this.speed = Optional.ofNullable(speed); return this; } @java.lang.Override @JsonSetter(value = "speed", nulls = Nulls.SKIP) - public _FinalStage speed(Optional speed) { + public _FinalStage speed(Optional speed) { this.speed = speed; return this; } diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java index 2a8dc12..3e032b1 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java @@ -6,7 +6,6 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -144,23 +143,7 @@ public CompletableFuture connect(V2ConnectOptions options) { "mip_opt_out", String.valueOf(options.getMipOptOut().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - // Array-valued query params (String | List unions) must serialize as repeated - // params (tag=a&tag=b), not a stringified list. The generated streaming template uses - // String.valueOf(...), which mangles a List into "[a, b]"; route through - // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. - QueryStringMapper.addQueryParameter( - urlBuilder, "tag", options.getTag().get().get(), true); - } - // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. - // The generated template only serializes the typed options and drops these otherwise. - // ConnectOptions is request-only (never deserialized), so this map holds only what the - // caller set via the builder. Routed through QueryStringMapper to match the REST path. - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); + urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); @@ -615,11 +598,11 @@ private void handleIncomingMessage(String json) { return; } } - // Unrecognized message type: forward-compatible no-op. The raw frame was - // already delivered to onMessage(String) above, so a newer server adding a - // benign control frame (e.g. a GA addition to this endpoint) must not surface - // as a fatal error to deployed clients. Routing it to onError would make a - // harmless frame look fatal to a voice agent; mirrors the JS/Python SDKs. + if (onErrorHandler != null) { + onErrorHandler.accept(new RuntimeException( + "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) + + "... Update your SDK version to support new message types.")); + } } catch (Exception e) { if (onErrorHandler != null) { onErrorHandler.accept(e); diff --git a/src/main/java/com/deepgram/types/Anthropic.java b/src/main/java/com/deepgram/types/Anthropic.java index 0c4b5d6..3205101 100644 --- a/src/main/java/com/deepgram/types/Anthropic.java +++ b/src/main/java/com/deepgram/types/Anthropic.java @@ -23,7 +23,7 @@ public final class Anthropic { private final Optional version; - private final AnthropicThinkProviderModel model; + private final AnthropicModel model; private final Optional temperature; @@ -31,7 +31,7 @@ public final class Anthropic { private Anthropic( Optional version, - AnthropicThinkProviderModel model, + AnthropicModel model, Optional temperature, Map additionalProperties) { this.version = version; @@ -57,7 +57,7 @@ public Optional getVersion() { * @return Anthropic model to use */ @JsonProperty("model") - public AnthropicThinkProviderModel getModel() { + public AnthropicModel getModel() { return model; } @@ -102,7 +102,7 @@ public interface ModelStage { /** *

Anthropic model to use

*/ - _FinalStage model(@NotNull AnthropicThinkProviderModel model); + _FinalStage model(@NotNull AnthropicModel model); Builder from(Anthropic other); } @@ -131,7 +131,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements ModelStage, _FinalStage { - private AnthropicThinkProviderModel model; + private AnthropicModel model; private Optional temperature = Optional.empty(); @@ -156,7 +156,7 @@ public Builder from(Anthropic other) { */ @java.lang.Override @JsonSetter("model") - public _FinalStage model(@NotNull AnthropicThinkProviderModel model) { + public _FinalStage model(@NotNull AnthropicModel model) { this.model = Objects.requireNonNull(model, "model must not be null"); return this; } diff --git a/src/main/java/com/deepgram/types/AnthropicThinkProviderModel.java b/src/main/java/com/deepgram/types/AnthropicModel.java similarity index 68% rename from src/main/java/com/deepgram/types/AnthropicThinkProviderModel.java rename to src/main/java/com/deepgram/types/AnthropicModel.java index 9aa4608..289315b 100644 --- a/src/main/java/com/deepgram/types/AnthropicThinkProviderModel.java +++ b/src/main/java/com/deepgram/types/AnthropicModel.java @@ -6,18 +6,18 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -public final class AnthropicThinkProviderModel { - public static final AnthropicThinkProviderModel CLAUDE_SONNET420250514 = - new AnthropicThinkProviderModel(Value.CLAUDE_SONNET420250514, "claude-sonnet-4-20250514"); +public final class AnthropicModel { + public static final AnthropicModel CLAUDE_SONNET420250514 = + new AnthropicModel(Value.CLAUDE_SONNET420250514, "claude-sonnet-4-20250514"); - public static final AnthropicThinkProviderModel CLAUDE35HAIKU_LATEST = - new AnthropicThinkProviderModel(Value.CLAUDE35HAIKU_LATEST, "claude-3-5-haiku-latest"); + public static final AnthropicModel CLAUDE35HAIKU_LATEST = + new AnthropicModel(Value.CLAUDE35HAIKU_LATEST, "claude-3-5-haiku-latest"); private final Value value; private final String string; - AnthropicThinkProviderModel(Value value, String string) { + AnthropicModel(Value value, String string) { this.value = value; this.string = string; } @@ -35,8 +35,7 @@ public String toString() { @java.lang.Override public boolean equals(Object other) { return (this == other) - || (other instanceof AnthropicThinkProviderModel - && this.string.equals(((AnthropicThinkProviderModel) other).string)); + || (other instanceof AnthropicModel && this.string.equals(((AnthropicModel) other).string)); } @java.lang.Override @@ -57,14 +56,14 @@ public T visit(Visitor visitor) { } @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static AnthropicThinkProviderModel valueOf(String value) { + public static AnthropicModel valueOf(String value) { switch (value) { case "claude-sonnet-4-20250514": return CLAUDE_SONNET420250514; case "claude-3-5-haiku-latest": return CLAUDE35HAIKU_LATEST; default: - return new AnthropicThinkProviderModel(Value.UNKNOWN, value); + return new AnthropicModel(Value.UNKNOWN, value); } } diff --git a/src/main/java/com/deepgram/types/Cartesia.java b/src/main/java/com/deepgram/types/Cartesia.java index 0a7bfae..410a491 100644 --- a/src/main/java/com/deepgram/types/Cartesia.java +++ b/src/main/java/com/deepgram/types/Cartesia.java @@ -23,9 +23,9 @@ public final class Cartesia { private final Optional version; - private final CartesiaSpeakProviderModelId modelId; + private final CartesiaModelId modelId; - private final CartesiaSpeakProviderVoice voice; + private final CartesiaVoice voice; private final Optional language; @@ -35,8 +35,8 @@ public final class Cartesia { private Cartesia( Optional version, - CartesiaSpeakProviderModelId modelId, - CartesiaSpeakProviderVoice voice, + CartesiaModelId modelId, + CartesiaVoice voice, Optional language, Optional volume, Map additionalProperties) { @@ -65,12 +65,12 @@ public Optional getVersion() { * @return Cartesia model ID */ @JsonProperty("model_id") - public CartesiaSpeakProviderModelId getModelId() { + public CartesiaModelId getModelId() { return modelId; } @JsonProperty("voice") - public CartesiaSpeakProviderVoice getVoice() { + public CartesiaVoice getVoice() { return voice; } @@ -127,13 +127,13 @@ public interface ModelIdStage { /** *

Cartesia model ID

*/ - VoiceStage modelId(@NotNull CartesiaSpeakProviderModelId modelId); + VoiceStage modelId(@NotNull CartesiaModelId modelId); Builder from(Cartesia other); } public interface VoiceStage { - _FinalStage voice(@NotNull CartesiaSpeakProviderVoice voice); + _FinalStage voice(@NotNull CartesiaVoice voice); } public interface _FinalStage { @@ -167,9 +167,9 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements ModelIdStage, VoiceStage, _FinalStage { - private CartesiaSpeakProviderModelId modelId; + private CartesiaModelId modelId; - private CartesiaSpeakProviderVoice voice; + private CartesiaVoice voice; private Optional volume = Optional.empty(); @@ -198,14 +198,14 @@ public Builder from(Cartesia other) { */ @java.lang.Override @JsonSetter("model_id") - public VoiceStage modelId(@NotNull CartesiaSpeakProviderModelId modelId) { + public VoiceStage modelId(@NotNull CartesiaModelId modelId) { this.modelId = Objects.requireNonNull(modelId, "modelId must not be null"); return this; } @java.lang.Override @JsonSetter("voice") - public _FinalStage voice(@NotNull CartesiaSpeakProviderVoice voice) { + public _FinalStage voice(@NotNull CartesiaVoice voice) { this.voice = Objects.requireNonNull(voice, "voice must not be null"); return this; } diff --git a/src/main/java/com/deepgram/types/CartesiaSpeakProviderModelId.java b/src/main/java/com/deepgram/types/CartesiaModelId.java similarity index 68% rename from src/main/java/com/deepgram/types/CartesiaSpeakProviderModelId.java rename to src/main/java/com/deepgram/types/CartesiaModelId.java index fddf607..81818d4 100644 --- a/src/main/java/com/deepgram/types/CartesiaSpeakProviderModelId.java +++ b/src/main/java/com/deepgram/types/CartesiaModelId.java @@ -6,17 +6,17 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -public final class CartesiaSpeakProviderModelId { - public static final CartesiaSpeakProviderModelId SONIC2 = new CartesiaSpeakProviderModelId(Value.SONIC2, "sonic-2"); +public final class CartesiaModelId { + public static final CartesiaModelId SONIC2 = new CartesiaModelId(Value.SONIC2, "sonic-2"); - public static final CartesiaSpeakProviderModelId SONIC_MULTILINGUAL = - new CartesiaSpeakProviderModelId(Value.SONIC_MULTILINGUAL, "sonic-multilingual"); + public static final CartesiaModelId SONIC_MULTILINGUAL = + new CartesiaModelId(Value.SONIC_MULTILINGUAL, "sonic-multilingual"); private final Value value; private final String string; - CartesiaSpeakProviderModelId(Value value, String string) { + CartesiaModelId(Value value, String string) { this.value = value; this.string = string; } @@ -34,8 +34,7 @@ public String toString() { @java.lang.Override public boolean equals(Object other) { return (this == other) - || (other instanceof CartesiaSpeakProviderModelId - && this.string.equals(((CartesiaSpeakProviderModelId) other).string)); + || (other instanceof CartesiaModelId && this.string.equals(((CartesiaModelId) other).string)); } @java.lang.Override @@ -56,14 +55,14 @@ public T visit(Visitor visitor) { } @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static CartesiaSpeakProviderModelId valueOf(String value) { + public static CartesiaModelId valueOf(String value) { switch (value) { case "sonic-2": return SONIC2; case "sonic-multilingual": return SONIC_MULTILINGUAL; default: - return new CartesiaSpeakProviderModelId(Value.UNKNOWN, value); + return new CartesiaModelId(Value.UNKNOWN, value); } } diff --git a/src/main/java/com/deepgram/types/CartesiaSpeakProviderVoice.java b/src/main/java/com/deepgram/types/CartesiaVoice.java similarity index 84% rename from src/main/java/com/deepgram/types/CartesiaSpeakProviderVoice.java rename to src/main/java/com/deepgram/types/CartesiaVoice.java index b788675..30417b5 100644 --- a/src/main/java/com/deepgram/types/CartesiaSpeakProviderVoice.java +++ b/src/main/java/com/deepgram/types/CartesiaVoice.java @@ -17,15 +17,15 @@ import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = CartesiaSpeakProviderVoice.Builder.class) -public final class CartesiaSpeakProviderVoice { +@JsonDeserialize(builder = CartesiaVoice.Builder.class) +public final class CartesiaVoice { private final String mode; private final String id; private final Map additionalProperties; - private CartesiaSpeakProviderVoice(String mode, String id, Map additionalProperties) { + private CartesiaVoice(String mode, String id, Map additionalProperties) { this.mode = mode; this.id = id; this.additionalProperties = additionalProperties; @@ -50,7 +50,7 @@ public String getId() { @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof CartesiaSpeakProviderVoice && equalTo((CartesiaSpeakProviderVoice) other); + return other instanceof CartesiaVoice && equalTo((CartesiaVoice) other); } @JsonAnyGetter @@ -58,7 +58,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(CartesiaSpeakProviderVoice other) { + private boolean equalTo(CartesiaVoice other) { return mode.equals(other.mode) && id.equals(other.id); } @@ -82,7 +82,7 @@ public interface ModeStage { */ IdStage mode(@NotNull String mode); - Builder from(CartesiaSpeakProviderVoice other); + Builder from(CartesiaVoice other); } public interface IdStage { @@ -93,7 +93,7 @@ public interface IdStage { } public interface _FinalStage { - CartesiaSpeakProviderVoice build(); + CartesiaVoice build(); _FinalStage additionalProperty(String key, Object value); @@ -112,7 +112,7 @@ public static final class Builder implements ModeStage, IdStage, _FinalStage { private Builder() {} @java.lang.Override - public Builder from(CartesiaSpeakProviderVoice other) { + public Builder from(CartesiaVoice other) { mode(other.getMode()); id(other.getId()); return this; @@ -141,8 +141,8 @@ public _FinalStage id(@NotNull String id) { } @java.lang.Override - public CartesiaSpeakProviderVoice build() { - return new CartesiaSpeakProviderVoice(mode, id, additionalProperties); + public CartesiaVoice build() { + return new CartesiaVoice(mode, id, additionalProperties); } @java.lang.Override diff --git a/src/main/java/com/deepgram/types/Deepgram.java b/src/main/java/com/deepgram/types/Deepgram.java index 849edda..78ed9a0 100644 --- a/src/main/java/com/deepgram/types/Deepgram.java +++ b/src/main/java/com/deepgram/types/Deepgram.java @@ -23,7 +23,7 @@ public final class Deepgram { private final Optional version; - private final DeepgramSpeakProviderModel model; + private final DeepgramModel model; private final Optional speed; @@ -31,7 +31,7 @@ public final class Deepgram { private Deepgram( Optional version, - DeepgramSpeakProviderModel model, + DeepgramModel model, Optional speed, Map additionalProperties) { this.version = version; @@ -54,15 +54,15 @@ public Optional getVersion() { } /** - * @return Deepgram TTS model. Aura models (version v1) use the aura-* voices; Flux TTS (version v2) uses the flux-{voice}-{language} voices (e.g. flux-alexis-en). + * @return Deepgram TTS model. Aura models (version v1) use the aura-* voices; Flux TTS (version v2) uses the flux-{voice}-{language} voices (e.g. flux-alexis-en). Defaults to flux-kit-en when agent.speak is omitted. */ @JsonProperty("model") - public DeepgramSpeakProviderModel getModel() { + public DeepgramModel getModel() { return model; } /** - * @return Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Not yet supported in all languages. + * @return Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Aura (version v1) accepts any value from 0.7 to 1.5. Flux TTS (version v2) accepts only 0.85, 0.9, 0.95, 1.0, 1.05, 1.1 and 1.15; another value ends the session with FAILED_TO_SPEAK. Not yet supported in all languages. */ @JsonProperty("speed") public Optional getSpeed() { @@ -100,9 +100,9 @@ public static ModelStage builder() { public interface ModelStage { /** - *

Deepgram TTS model. Aura models (version v1) use the aura-* voices; Flux TTS (version v2) uses the flux-{voice}-{language} voices (e.g. flux-alexis-en).

+ *

Deepgram TTS model. Aura models (version v1) use the aura-* voices; Flux TTS (version v2) uses the flux-{voice}-{language} voices (e.g. flux-alexis-en). Defaults to flux-kit-en when agent.speak is omitted.

*/ - _FinalStage model(@NotNull DeepgramSpeakProviderModel model); + _FinalStage model(@NotNull DeepgramModel model); Builder from(Deepgram other); } @@ -122,7 +122,7 @@ public interface _FinalStage { _FinalStage version(String version); /** - *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Not yet supported in all languages.

+ *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Aura (version v1) accepts any value from 0.7 to 1.5. Flux TTS (version v2) accepts only 0.85, 0.9, 0.95, 1.0, 1.05, 1.1 and 1.15; another value ends the session with FAILED_TO_SPEAK. Not yet supported in all languages.

*/ _FinalStage speed(Optional speed); @@ -131,7 +131,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements ModelStage, _FinalStage { - private DeepgramSpeakProviderModel model; + private DeepgramModel model; private Optional speed = Optional.empty(); @@ -151,18 +151,18 @@ public Builder from(Deepgram other) { } /** - *

Deepgram TTS model. Aura models (version v1) use the aura-* voices; Flux TTS (version v2) uses the flux-{voice}-{language} voices (e.g. flux-alexis-en).

+ *

Deepgram TTS model. Aura models (version v1) use the aura-* voices; Flux TTS (version v2) uses the flux-{voice}-{language} voices (e.g. flux-alexis-en). Defaults to flux-kit-en when agent.speak is omitted.

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

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Not yet supported in all languages.

+ *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Aura (version v1) accepts any value from 0.7 to 1.5. Flux TTS (version v2) accepts only 0.85, 0.9, 0.95, 1.0, 1.05, 1.1 and 1.15; another value ends the session with FAILED_TO_SPEAK. Not yet supported in all languages.

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

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Not yet supported in all languages.

+ *

Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Aura (version v1) accepts any value from 0.7 to 1.5. Flux TTS (version v2) accepts only 0.85, 0.9, 0.95, 1.0, 1.05, 1.1 and 1.15; another value ends the session with FAILED_TO_SPEAK. Not yet supported in all languages.

*/ @java.lang.Override @JsonSetter(value = "speed", nulls = Nulls.SKIP) diff --git a/src/main/java/com/deepgram/types/DeepgramSpeakProviderModel.java b/src/main/java/com/deepgram/types/DeepgramModel.java similarity index 50% rename from src/main/java/com/deepgram/types/DeepgramSpeakProviderModel.java rename to src/main/java/com/deepgram/types/DeepgramModel.java index 0fe7645..fc7c08c 100644 --- a/src/main/java/com/deepgram/types/DeepgramSpeakProviderModel.java +++ b/src/main/java/com/deepgram/types/DeepgramModel.java @@ -6,237 +6,218 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -public final class DeepgramSpeakProviderModel { - public static final DeepgramSpeakProviderModel AURA2SIRIO_ES = - new DeepgramSpeakProviderModel(Value.AURA2SIRIO_ES, "aura-2-sirio-es"); +public final class DeepgramModel { + public static final DeepgramModel AURA2SIRIO_ES = new DeepgramModel(Value.AURA2SIRIO_ES, "aura-2-sirio-es"); - public static final DeepgramSpeakProviderModel AURA2HERA_EN = - new DeepgramSpeakProviderModel(Value.AURA2HERA_EN, "aura-2-hera-en"); + public static final DeepgramModel AURA2HERA_EN = new DeepgramModel(Value.AURA2HERA_EN, "aura-2-hera-en"); - public static final DeepgramSpeakProviderModel AURA2ASTERIA_EN = - new DeepgramSpeakProviderModel(Value.AURA2ASTERIA_EN, "aura-2-asteria-en"); + public static final DeepgramModel FLUX_KELSEY_EN = new DeepgramModel(Value.FLUX_KELSEY_EN, "flux-kelsey-en"); - public static final DeepgramSpeakProviderModel AURA2HARMONIA_EN = - new DeepgramSpeakProviderModel(Value.AURA2HARMONIA_EN, "aura-2-harmonia-en"); + public static final DeepgramModel FLUX_SIENNA_EN = new DeepgramModel(Value.FLUX_SIENNA_EN, "flux-sienna-en"); - public static final DeepgramSpeakProviderModel AURA2CARINA_ES = - new DeepgramSpeakProviderModel(Value.AURA2CARINA_ES, "aura-2-carina-es"); + public static final DeepgramModel AURA2HARMONIA_EN = + new DeepgramModel(Value.AURA2HARMONIA_EN, "aura-2-harmonia-en"); - public static final DeepgramSpeakProviderModel AURA_ZEUS_EN = - new DeepgramSpeakProviderModel(Value.AURA_ZEUS_EN, "aura-zeus-en"); + public static final DeepgramModel AURA2CARINA_ES = new DeepgramModel(Value.AURA2CARINA_ES, "aura-2-carina-es"); - public static final DeepgramSpeakProviderModel AURA2HERMES_EN = - new DeepgramSpeakProviderModel(Value.AURA2HERMES_EN, "aura-2-hermes-en"); + public static final DeepgramModel FLUX_MEENA_EN = new DeepgramModel(Value.FLUX_MEENA_EN, "flux-meena-en"); - public static final DeepgramSpeakProviderModel AURA2SELENA_ES = - new DeepgramSpeakProviderModel(Value.AURA2SELENA_ES, "aura-2-selena-es"); + public static final DeepgramModel FLUX_MEGHAN_EN = new DeepgramModel(Value.FLUX_MEGHAN_EN, "flux-meghan-en"); - public static final DeepgramSpeakProviderModel AURA2NEPTUNE_EN = - new DeepgramSpeakProviderModel(Value.AURA2NEPTUNE_EN, "aura-2-neptune-en"); + public static final DeepgramModel AURA_ZEUS_EN = new DeepgramModel(Value.AURA_ZEUS_EN, "aura-zeus-en"); - public static final DeepgramSpeakProviderModel FLUX_RUFUS_EN = - new DeepgramSpeakProviderModel(Value.FLUX_RUFUS_EN, "flux-rufus-en"); + public static final DeepgramModel AURA2HERMES_EN = new DeepgramModel(Value.AURA2HERMES_EN, "aura-2-hermes-en"); - public static final DeepgramSpeakProviderModel AURA2CALLISTA_EN = - new DeepgramSpeakProviderModel(Value.AURA2CALLISTA_EN, "aura-2-callista-en"); + public static final DeepgramModel FLUX_CONOR_EN = new DeepgramModel(Value.FLUX_CONOR_EN, "flux-conor-en"); - public static final DeepgramSpeakProviderModel AURA2AURORA_EN = - new DeepgramSpeakProviderModel(Value.AURA2AURORA_EN, "aura-2-aurora-en"); + public static final DeepgramModel AURA2NEPTUNE_EN = new DeepgramModel(Value.AURA2NEPTUNE_EN, "aura-2-neptune-en"); - public static final DeepgramSpeakProviderModel AURA2OPHELIA_EN = - new DeepgramSpeakProviderModel(Value.AURA2OPHELIA_EN, "aura-2-ophelia-en"); + public static final DeepgramModel FLUX_BROOKE_EN = new DeepgramModel(Value.FLUX_BROOKE_EN, "flux-brooke-en"); - public static final DeepgramSpeakProviderModel FLUX_BRUCE_EN = - new DeepgramSpeakProviderModel(Value.FLUX_BRUCE_EN, "flux-bruce-en"); + public static final DeepgramModel FLUX_RUFUS_EN = new DeepgramModel(Value.FLUX_RUFUS_EN, "flux-rufus-en"); - public static final DeepgramSpeakProviderModel AURA2APOLLO_EN = - new DeepgramSpeakProviderModel(Value.AURA2APOLLO_EN, "aura-2-apollo-en"); + public static final DeepgramModel FLUX_WES_EN = new DeepgramModel(Value.FLUX_WES_EN, "flux-wes-en"); - public static final DeepgramSpeakProviderModel FLUX_ALEXIS_EN = - new DeepgramSpeakProviderModel(Value.FLUX_ALEXIS_EN, "flux-alexis-en"); + public static final DeepgramModel AURA2CALLISTA_EN = + new DeepgramModel(Value.AURA2CALLISTA_EN, "aura-2-callista-en"); - public static final DeepgramSpeakProviderModel AURA_LUNA_EN = - new DeepgramSpeakProviderModel(Value.AURA_LUNA_EN, "aura-luna-en"); + public static final DeepgramModel AURA2AURORA_EN = new DeepgramModel(Value.AURA2AURORA_EN, "aura-2-aurora-en"); - public static final DeepgramSpeakProviderModel AURA_ORPHEUS_EN = - new DeepgramSpeakProviderModel(Value.AURA_ORPHEUS_EN, "aura-orpheus-en"); + public static final DeepgramModel AURA2OPHELIA_EN = new DeepgramModel(Value.AURA2OPHELIA_EN, "aura-2-ophelia-en"); - public static final DeepgramSpeakProviderModel AURA2DELIA_EN = - new DeepgramSpeakProviderModel(Value.AURA2DELIA_EN, "aura-2-delia-en"); + public static final DeepgramModel FLUX_BRUCE_EN = new DeepgramModel(Value.FLUX_BRUCE_EN, "flux-bruce-en"); - public static final DeepgramSpeakProviderModel AURA2MARS_EN = - new DeepgramSpeakProviderModel(Value.AURA2MARS_EN, "aura-2-mars-en"); + public static final DeepgramModel AURA2APOLLO_EN = new DeepgramModel(Value.AURA2APOLLO_EN, "aura-2-apollo-en"); - public static final DeepgramSpeakProviderModel AURA2AMALTHEA_EN = - new DeepgramSpeakProviderModel(Value.AURA2AMALTHEA_EN, "aura-2-amalthea-en"); + public static final DeepgramModel AURA2DELIA_EN = new DeepgramModel(Value.AURA2DELIA_EN, "aura-2-delia-en"); - public static final DeepgramSpeakProviderModel AURA_HERA_EN = - new DeepgramSpeakProviderModel(Value.AURA_HERA_EN, "aura-hera-en"); + public static final DeepgramModel AURA2MARS_EN = new DeepgramModel(Value.AURA2MARS_EN, "aura-2-mars-en"); - public static final DeepgramSpeakProviderModel AURA2SELENE_EN = - new DeepgramSpeakProviderModel(Value.AURA2SELENE_EN, "aura-2-selene-en"); + public static final DeepgramModel AURA_HERA_EN = new DeepgramModel(Value.AURA_HERA_EN, "aura-hera-en"); - public static final DeepgramSpeakProviderModel FLUX_MARCUS_EN = - new DeepgramSpeakProviderModel(Value.FLUX_MARCUS_EN, "flux-marcus-en"); + public static final DeepgramModel FLUX_MAEVE_EN = new DeepgramModel(Value.FLUX_MAEVE_EN, "flux-maeve-en"); - public static final DeepgramSpeakProviderModel FLUX_SHARON_EN = - new DeepgramSpeakProviderModel(Value.FLUX_SHARON_EN, "flux-sharon-en"); + public static final DeepgramModel FLUX_MARCUS_EN = new DeepgramModel(Value.FLUX_MARCUS_EN, "flux-marcus-en"); - public static final DeepgramSpeakProviderModel AURA2ARIES_EN = - new DeepgramSpeakProviderModel(Value.AURA2ARIES_EN, "aura-2-aries-en"); + public static final DeepgramModel AURA2ARIES_EN = new DeepgramModel(Value.AURA2ARIES_EN, "aura-2-aries-en"); - public static final DeepgramSpeakProviderModel AURA2JUNO_EN = - new DeepgramSpeakProviderModel(Value.AURA2JUNO_EN, "aura-2-juno-en"); + public static final DeepgramModel FLUX_PAIGE_EN = new DeepgramModel(Value.FLUX_PAIGE_EN, "flux-paige-en"); - public static final DeepgramSpeakProviderModel AURA2LUNA_EN = - new DeepgramSpeakProviderModel(Value.AURA2LUNA_EN, "aura-2-luna-en"); + public static final DeepgramModel AURA2JUNO_EN = new DeepgramModel(Value.AURA2JUNO_EN, "aura-2-juno-en"); - public static final DeepgramSpeakProviderModel FLUX_DREW_EN = - new DeepgramSpeakProviderModel(Value.FLUX_DREW_EN, "flux-drew-en"); + public static final DeepgramModel FLUX_DREW_EN = new DeepgramModel(Value.FLUX_DREW_EN, "flux-drew-en"); - public static final DeepgramSpeakProviderModel AURA2JAVIER_ES = - new DeepgramSpeakProviderModel(Value.AURA2JAVIER_ES, "aura-2-javier-es"); + public static final DeepgramModel AURA2JAVIER_ES = new DeepgramModel(Value.AURA2JAVIER_ES, "aura-2-javier-es"); - public static final DeepgramSpeakProviderModel AURA2PHOEBE_EN = - new DeepgramSpeakProviderModel(Value.AURA2PHOEBE_EN, "aura-2-phoebe-en"); + public static final DeepgramModel AURA2ANDROMEDA_EN = + new DeepgramModel(Value.AURA2ANDROMEDA_EN, "aura-2-andromeda-en"); - public static final DeepgramSpeakProviderModel AURA2DIANA_ES = - new DeepgramSpeakProviderModel(Value.AURA2DIANA_ES, "aura-2-diana-es"); + public static final DeepgramModel AURA2SATURN_EN = new DeepgramModel(Value.AURA2SATURN_EN, "aura-2-saturn-en"); - public static final DeepgramSpeakProviderModel AURA_ORION_EN = - new DeepgramSpeakProviderModel(Value.AURA_ORION_EN, "aura-orion-en"); + public static final DeepgramModel AURA2IRIS_EN = new DeepgramModel(Value.AURA2IRIS_EN, "aura-2-iris-en"); - public static final DeepgramSpeakProviderModel AURA2ANDROMEDA_EN = - new DeepgramSpeakProviderModel(Value.AURA2ANDROMEDA_EN, "aura-2-andromeda-en"); + public static final DeepgramModel AURA_ANGUS_EN = new DeepgramModel(Value.AURA_ANGUS_EN, "aura-angus-en"); - public static final DeepgramSpeakProviderModel AURA2ATHENA_EN = - new DeepgramSpeakProviderModel(Value.AURA2ATHENA_EN, "aura-2-athena-en"); + public static final DeepgramModel FLUX_KAI_EN = new DeepgramModel(Value.FLUX_KAI_EN, "flux-kai-en"); - public static final DeepgramSpeakProviderModel AURA2SATURN_EN = - new DeepgramSpeakProviderModel(Value.AURA2SATURN_EN, "aura-2-saturn-en"); + public static final DeepgramModel FLUX_BRITTANY_EN = new DeepgramModel(Value.FLUX_BRITTANY_EN, "flux-brittany-en"); - public static final DeepgramSpeakProviderModel AURA_ARCAS_EN = - new DeepgramSpeakProviderModel(Value.AURA_ARCAS_EN, "aura-arcas-en"); + public static final DeepgramModel FLUX_SEAN_EN = new DeepgramModel(Value.FLUX_SEAN_EN, "flux-sean-en"); - public static final DeepgramSpeakProviderModel AURA2THEIA_EN = - new DeepgramSpeakProviderModel(Value.AURA2THEIA_EN, "aura-2-theia-en"); + public static final DeepgramModel AURA2HELENA_EN = new DeepgramModel(Value.AURA2HELENA_EN, "aura-2-helena-en"); - public static final DeepgramSpeakProviderModel AURA2IRIS_EN = - new DeepgramSpeakProviderModel(Value.AURA2IRIS_EN, "aura-2-iris-en"); + public static final DeepgramModel FLUX_HALEY_EN = new DeepgramModel(Value.FLUX_HALEY_EN, "flux-haley-en"); - public static final DeepgramSpeakProviderModel AURA_ANGUS_EN = - new DeepgramSpeakProviderModel(Value.AURA_ANGUS_EN, "aura-angus-en"); + public static final DeepgramModel AURA_STELLA_EN = new DeepgramModel(Value.AURA_STELLA_EN, "aura-stella-en"); - public static final DeepgramSpeakProviderModel FLUX_HEATHER_EN = - new DeepgramSpeakProviderModel(Value.FLUX_HEATHER_EN, "flux-heather-en"); + public static final DeepgramModel FLUX_JACK_EN = new DeepgramModel(Value.FLUX_JACK_EN, "flux-jack-en"); - public static final DeepgramSpeakProviderModel AURA2JUPITER_EN = - new DeepgramSpeakProviderModel(Value.AURA2JUPITER_EN, "aura-2-jupiter-en"); + public static final DeepgramModel AURA2DRACO_EN = new DeepgramModel(Value.AURA2DRACO_EN, "aura-2-draco-en"); - public static final DeepgramSpeakProviderModel AURA2AQUILA_ES = - new DeepgramSpeakProviderModel(Value.AURA2AQUILA_ES, "aura-2-aquila-es"); + public static final DeepgramModel FLUX_PRIYA_EN = new DeepgramModel(Value.FLUX_PRIYA_EN, "flux-priya-en"); - public static final DeepgramSpeakProviderModel AURA2CORA_EN = - new DeepgramSpeakProviderModel(Value.AURA2CORA_EN, "aura-2-cora-en"); + public static final DeepgramModel AURA_HELIOS_EN = new DeepgramModel(Value.AURA_HELIOS_EN, "aura-helios-en"); - public static final DeepgramSpeakProviderModel AURA2CORDELIA_EN = - new DeepgramSpeakProviderModel(Value.AURA2CORDELIA_EN, "aura-2-cordelia-en"); + public static final DeepgramModel AURA2JANUS_EN = new DeepgramModel(Value.AURA2JANUS_EN, "aura-2-janus-en"); - public static final DeepgramSpeakProviderModel AURA2ATLAS_EN = - new DeepgramSpeakProviderModel(Value.AURA2ATLAS_EN, "aura-2-atlas-en"); + public static final DeepgramModel AURA2NESTOR_ES = new DeepgramModel(Value.AURA2NESTOR_ES, "aura-2-nestor-es"); - public static final DeepgramSpeakProviderModel AURA2HELENA_EN = - new DeepgramSpeakProviderModel(Value.AURA2HELENA_EN, "aura-2-helena-en"); + public static final DeepgramModel AURA2ORION_EN = new DeepgramModel(Value.AURA2ORION_EN, "aura-2-orion-en"); - public static final DeepgramSpeakProviderModel FLUX_HALEY_EN = - new DeepgramSpeakProviderModel(Value.FLUX_HALEY_EN, "flux-haley-en"); + public static final DeepgramModel AURA2MINERVA_EN = new DeepgramModel(Value.AURA2MINERVA_EN, "aura-2-minerva-en"); - public static final DeepgramSpeakProviderModel AURA_STELLA_EN = - new DeepgramSpeakProviderModel(Value.AURA_STELLA_EN, "aura-stella-en"); + public static final DeepgramModel AURA2ALVARO_ES = new DeepgramModel(Value.AURA2ALVARO_ES, "aura-2-alvaro-es"); - public static final DeepgramSpeakProviderModel FLUX_JACK_EN = - new DeepgramSpeakProviderModel(Value.FLUX_JACK_EN, "flux-jack-en"); + public static final DeepgramModel AURA_PERSEUS_EN = new DeepgramModel(Value.AURA_PERSEUS_EN, "aura-perseus-en"); - public static final DeepgramSpeakProviderModel AURA2DRACO_EN = - new DeepgramSpeakProviderModel(Value.AURA2DRACO_EN, "aura-2-draco-en"); + public static final DeepgramModel AURA2VESTA_EN = new DeepgramModel(Value.AURA2VESTA_EN, "aura-2-vesta-en"); - public static final DeepgramSpeakProviderModel AURA2HYPERION_EN = - new DeepgramSpeakProviderModel(Value.AURA2HYPERION_EN, "aura-2-hyperion-en"); + public static final DeepgramModel FLUX_COLE_EN = new DeepgramModel(Value.FLUX_COLE_EN, "flux-cole-en"); - public static final DeepgramSpeakProviderModel AURA2CELESTE_ES = - new DeepgramSpeakProviderModel(Value.AURA2CELESTE_ES, "aura-2-celeste-es"); + public static final DeepgramModel AURA2ZEUS_EN = new DeepgramModel(Value.AURA2ZEUS_EN, "aura-2-zeus-en"); - public static final DeepgramSpeakProviderModel FLUX_PRIYA_EN = - new DeepgramSpeakProviderModel(Value.FLUX_PRIYA_EN, "flux-priya-en"); + public static final DeepgramModel AURA2ORPHEUS_EN = new DeepgramModel(Value.AURA2ORPHEUS_EN, "aura-2-orpheus-en"); - public static final DeepgramSpeakProviderModel AURA_HELIOS_EN = - new DeepgramSpeakProviderModel(Value.AURA_HELIOS_EN, "aura-helios-en"); + public static final DeepgramModel AURA2THALIA_EN = new DeepgramModel(Value.AURA2THALIA_EN, "aura-2-thalia-en"); - public static final DeepgramSpeakProviderModel AURA2PLUTO_EN = - new DeepgramSpeakProviderModel(Value.AURA2PLUTO_EN, "aura-2-pluto-en"); + public static final DeepgramModel FLUX_NAVEEN_EN = new DeepgramModel(Value.FLUX_NAVEEN_EN, "flux-naveen-en"); - public static final DeepgramSpeakProviderModel AURA2JANUS_EN = - new DeepgramSpeakProviderModel(Value.AURA2JANUS_EN, "aura-2-janus-en"); + public static final DeepgramModel AURA2ASTERIA_EN = new DeepgramModel(Value.AURA2ASTERIA_EN, "aura-2-asteria-en"); - public static final DeepgramSpeakProviderModel AURA2NESTOR_ES = - new DeepgramSpeakProviderModel(Value.AURA2NESTOR_ES, "aura-2-nestor-es"); + public static final DeepgramModel AURA2SELENA_ES = new DeepgramModel(Value.AURA2SELENA_ES, "aura-2-selena-es"); - public static final DeepgramSpeakProviderModel AURA2ARCAS_EN = - new DeepgramSpeakProviderModel(Value.AURA2ARCAS_EN, "aura-2-arcas-en"); + public static final DeepgramModel FLUX_MARCELO_EN = new DeepgramModel(Value.FLUX_MARCELO_EN, "flux-marcelo-en"); - public static final DeepgramSpeakProviderModel AURA2ORION_EN = - new DeepgramSpeakProviderModel(Value.AURA2ORION_EN, "aura-2-orion-en"); + public static final DeepgramModel FLUX_ELISE_EN = new DeepgramModel(Value.FLUX_ELISE_EN, "flux-elise-en"); - public static final DeepgramSpeakProviderModel FLUX_RENEE_EN = - new DeepgramSpeakProviderModel(Value.FLUX_RENEE_EN, "flux-renee-en"); + public static final DeepgramModel FLUX_ALEXIS_EN = new DeepgramModel(Value.FLUX_ALEXIS_EN, "flux-alexis-en"); - public static final DeepgramSpeakProviderModel AURA_ATHENA_EN = - new DeepgramSpeakProviderModel(Value.AURA_ATHENA_EN, "aura-athena-en"); + public static final DeepgramModel AURA_LUNA_EN = new DeepgramModel(Value.AURA_LUNA_EN, "aura-luna-en"); - public static final DeepgramSpeakProviderModel AURA2ODYSSEUS_EN = - new DeepgramSpeakProviderModel(Value.AURA2ODYSSEUS_EN, "aura-2-odysseus-en"); + public static final DeepgramModel AURA_ORPHEUS_EN = new DeepgramModel(Value.AURA_ORPHEUS_EN, "aura-orpheus-en"); - public static final DeepgramSpeakProviderModel AURA2PANDORA_EN = - new DeepgramSpeakProviderModel(Value.AURA2PANDORA_EN, "aura-2-pandora-en"); + public static final DeepgramModel AURA2AMALTHEA_EN = + new DeepgramModel(Value.AURA2AMALTHEA_EN, "aura-2-amalthea-en"); - public static final DeepgramSpeakProviderModel AURA2MINERVA_EN = - new DeepgramSpeakProviderModel(Value.AURA2MINERVA_EN, "aura-2-minerva-en"); + public static final DeepgramModel FLUX_DONOVAN_EN = new DeepgramModel(Value.FLUX_DONOVAN_EN, "flux-donovan-en"); - public static final DeepgramSpeakProviderModel AURA2ALVARO_ES = - new DeepgramSpeakProviderModel(Value.AURA2ALVARO_ES, "aura-2-alvaro-es"); + public static final DeepgramModel AURA2SELENE_EN = new DeepgramModel(Value.AURA2SELENE_EN, "aura-2-selene-en"); - public static final DeepgramSpeakProviderModel AURA_PERSEUS_EN = - new DeepgramSpeakProviderModel(Value.AURA_PERSEUS_EN, "aura-perseus-en"); + public static final DeepgramModel FLUX_TANNER_EN = new DeepgramModel(Value.FLUX_TANNER_EN, "flux-tanner-en"); - public static final DeepgramSpeakProviderModel AURA2VESTA_EN = - new DeepgramSpeakProviderModel(Value.AURA2VESTA_EN, "aura-2-vesta-en"); + public static final DeepgramModel FLUX_SHARON_EN = new DeepgramModel(Value.FLUX_SHARON_EN, "flux-sharon-en"); - public static final DeepgramSpeakProviderModel FLUX_COLE_EN = - new DeepgramSpeakProviderModel(Value.FLUX_COLE_EN, "flux-cole-en"); + public static final DeepgramModel AURA2LUNA_EN = new DeepgramModel(Value.AURA2LUNA_EN, "aura-2-luna-en"); - public static final DeepgramSpeakProviderModel AURA_ASTERIA_EN = - new DeepgramSpeakProviderModel(Value.AURA_ASTERIA_EN, "aura-asteria-en"); + public static final DeepgramModel AURA2PHOEBE_EN = new DeepgramModel(Value.AURA2PHOEBE_EN, "aura-2-phoebe-en"); - public static final DeepgramSpeakProviderModel AURA2ZEUS_EN = - new DeepgramSpeakProviderModel(Value.AURA2ZEUS_EN, "aura-2-zeus-en"); + public static final DeepgramModel FLUX_HANNAH_EN = new DeepgramModel(Value.FLUX_HANNAH_EN, "flux-hannah-en"); - public static final DeepgramSpeakProviderModel AURA2ELECTRA_EN = - new DeepgramSpeakProviderModel(Value.AURA2ELECTRA_EN, "aura-2-electra-en"); + public static final DeepgramModel AURA2DIANA_ES = new DeepgramModel(Value.AURA2DIANA_ES, "aura-2-diana-es"); - public static final DeepgramSpeakProviderModel AURA2ORPHEUS_EN = - new DeepgramSpeakProviderModel(Value.AURA2ORPHEUS_EN, "aura-2-orpheus-en"); + public static final DeepgramModel AURA_ORION_EN = new DeepgramModel(Value.AURA_ORION_EN, "aura-orion-en"); - public static final DeepgramSpeakProviderModel AURA2ESTRELLA_ES = - new DeepgramSpeakProviderModel(Value.AURA2ESTRELLA_ES, "aura-2-estrella-es"); + public static final DeepgramModel AURA2ATHENA_EN = new DeepgramModel(Value.AURA2ATHENA_EN, "aura-2-athena-en"); - public static final DeepgramSpeakProviderModel AURA2THALIA_EN = - new DeepgramSpeakProviderModel(Value.AURA2THALIA_EN, "aura-2-thalia-en"); + public static final DeepgramModel AURA_ARCAS_EN = new DeepgramModel(Value.AURA_ARCAS_EN, "aura-arcas-en"); + + public static final DeepgramModel AURA2THEIA_EN = new DeepgramModel(Value.AURA2THEIA_EN, "aura-2-theia-en"); + + public static final DeepgramModel FLUX_WADE_EN = new DeepgramModel(Value.FLUX_WADE_EN, "flux-wade-en"); + + public static final DeepgramModel FLUX_HEATHER_EN = new DeepgramModel(Value.FLUX_HEATHER_EN, "flux-heather-en"); + + public static final DeepgramModel FLUX_CLIFF_EN = new DeepgramModel(Value.FLUX_CLIFF_EN, "flux-cliff-en"); + + public static final DeepgramModel AURA2JUPITER_EN = new DeepgramModel(Value.AURA2JUPITER_EN, "aura-2-jupiter-en"); + + public static final DeepgramModel FLUX_KIT_EN = new DeepgramModel(Value.FLUX_KIT_EN, "flux-kit-en"); + + public static final DeepgramModel AURA2AQUILA_ES = new DeepgramModel(Value.AURA2AQUILA_ES, "aura-2-aquila-es"); + + public static final DeepgramModel AURA2CORA_EN = new DeepgramModel(Value.AURA2CORA_EN, "aura-2-cora-en"); + + public static final DeepgramModel AURA2CORDELIA_EN = + new DeepgramModel(Value.AURA2CORDELIA_EN, "aura-2-cordelia-en"); + + public static final DeepgramModel AURA2ATLAS_EN = new DeepgramModel(Value.AURA2ATLAS_EN, "aura-2-atlas-en"); + + public static final DeepgramModel AURA2HYPERION_EN = + new DeepgramModel(Value.AURA2HYPERION_EN, "aura-2-hyperion-en"); + + public static final DeepgramModel AURA2CELESTE_ES = new DeepgramModel(Value.AURA2CELESTE_ES, "aura-2-celeste-es"); + + public static final DeepgramModel AURA2PLUTO_EN = new DeepgramModel(Value.AURA2PLUTO_EN, "aura-2-pluto-en"); + + public static final DeepgramModel FLUX_BREE_EN = new DeepgramModel(Value.FLUX_BREE_EN, "flux-bree-en"); + + public static final DeepgramModel AURA2ARCAS_EN = new DeepgramModel(Value.AURA2ARCAS_EN, "aura-2-arcas-en"); + + public static final DeepgramModel FLUX_COLIN_EN = new DeepgramModel(Value.FLUX_COLIN_EN, "flux-colin-en"); + + public static final DeepgramModel AURA_ATHENA_EN = new DeepgramModel(Value.AURA_ATHENA_EN, "aura-athena-en"); + + public static final DeepgramModel AURA2ODYSSEUS_EN = + new DeepgramModel(Value.AURA2ODYSSEUS_EN, "aura-2-odysseus-en"); + + public static final DeepgramModel AURA2PANDORA_EN = new DeepgramModel(Value.AURA2PANDORA_EN, "aura-2-pandora-en"); + + public static final DeepgramModel FLUX_MILES_EN = new DeepgramModel(Value.FLUX_MILES_EN, "flux-miles-en"); + + public static final DeepgramModel AURA_ASTERIA_EN = new DeepgramModel(Value.AURA_ASTERIA_EN, "aura-asteria-en"); + + public static final DeepgramModel AURA2ELECTRA_EN = new DeepgramModel(Value.AURA2ELECTRA_EN, "aura-2-electra-en"); + + public static final DeepgramModel FLUX_GEMMA_EN = new DeepgramModel(Value.FLUX_GEMMA_EN, "flux-gemma-en"); + + public static final DeepgramModel AURA2ESTRELLA_ES = + new DeepgramModel(Value.AURA2ESTRELLA_ES, "aura-2-estrella-es"); private final Value value; private final String string; - DeepgramSpeakProviderModel(Value value, String string) { + DeepgramModel(Value value, String string) { this.value = value; this.string = string; } @@ -254,8 +235,7 @@ public String toString() { @java.lang.Override public boolean equals(Object other) { return (this == other) - || (other instanceof DeepgramSpeakProviderModel - && this.string.equals(((DeepgramSpeakProviderModel) other).string)); + || (other instanceof DeepgramModel && this.string.equals(((DeepgramModel) other).string)); } @java.lang.Override @@ -269,22 +249,32 @@ public T visit(Visitor visitor) { return visitor.visitAura2SirioEs(); case AURA2HERA_EN: return visitor.visitAura2HeraEn(); - case AURA2ASTERIA_EN: - return visitor.visitAura2AsteriaEn(); + case FLUX_KELSEY_EN: + return visitor.visitFluxKelseyEn(); + case FLUX_SIENNA_EN: + return visitor.visitFluxSiennaEn(); case AURA2HARMONIA_EN: return visitor.visitAura2HarmoniaEn(); case AURA2CARINA_ES: return visitor.visitAura2CarinaEs(); + case FLUX_MEENA_EN: + return visitor.visitFluxMeenaEn(); + case FLUX_MEGHAN_EN: + return visitor.visitFluxMeghanEn(); case AURA_ZEUS_EN: return visitor.visitAuraZeusEn(); case AURA2HERMES_EN: return visitor.visitAura2HermesEn(); - case AURA2SELENA_ES: - return visitor.visitAura2SelenaEs(); + case FLUX_CONOR_EN: + return visitor.visitFluxConorEn(); case AURA2NEPTUNE_EN: return visitor.visitAura2NeptuneEn(); + case FLUX_BROOKE_EN: + return visitor.visitFluxBrookeEn(); case FLUX_RUFUS_EN: return visitor.visitFluxRufusEn(); + case FLUX_WES_EN: + return visitor.visitFluxWesEn(); case AURA2CALLISTA_EN: return visitor.visitAura2CallistaEn(); case AURA2AURORA_EN: @@ -295,68 +285,40 @@ public T visit(Visitor visitor) { return visitor.visitFluxBruceEn(); case AURA2APOLLO_EN: return visitor.visitAura2ApolloEn(); - case FLUX_ALEXIS_EN: - return visitor.visitFluxAlexisEn(); - case AURA_LUNA_EN: - return visitor.visitAuraLunaEn(); - case AURA_ORPHEUS_EN: - return visitor.visitAuraOrpheusEn(); case AURA2DELIA_EN: return visitor.visitAura2DeliaEn(); case AURA2MARS_EN: return visitor.visitAura2MarsEn(); - case AURA2AMALTHEA_EN: - return visitor.visitAura2AmaltheaEn(); case AURA_HERA_EN: return visitor.visitAuraHeraEn(); - case AURA2SELENE_EN: - return visitor.visitAura2SeleneEn(); + case FLUX_MAEVE_EN: + return visitor.visitFluxMaeveEn(); case FLUX_MARCUS_EN: return visitor.visitFluxMarcusEn(); - case FLUX_SHARON_EN: - return visitor.visitFluxSharonEn(); case AURA2ARIES_EN: return visitor.visitAura2AriesEn(); + case FLUX_PAIGE_EN: + return visitor.visitFluxPaigeEn(); case AURA2JUNO_EN: return visitor.visitAura2JunoEn(); - case AURA2LUNA_EN: - return visitor.visitAura2LunaEn(); case FLUX_DREW_EN: return visitor.visitFluxDrewEn(); case AURA2JAVIER_ES: return visitor.visitAura2JavierEs(); - case AURA2PHOEBE_EN: - return visitor.visitAura2PhoebeEn(); - case AURA2DIANA_ES: - return visitor.visitAura2DianaEs(); - case AURA_ORION_EN: - return visitor.visitAuraOrionEn(); case AURA2ANDROMEDA_EN: return visitor.visitAura2AndromedaEn(); - case AURA2ATHENA_EN: - return visitor.visitAura2AthenaEn(); case AURA2SATURN_EN: return visitor.visitAura2SaturnEn(); - case AURA_ARCAS_EN: - return visitor.visitAuraArcasEn(); - case AURA2THEIA_EN: - return visitor.visitAura2TheiaEn(); case AURA2IRIS_EN: return visitor.visitAura2IrisEn(); case AURA_ANGUS_EN: return visitor.visitAuraAngusEn(); - case FLUX_HEATHER_EN: - return visitor.visitFluxHeatherEn(); - case AURA2JUPITER_EN: - return visitor.visitAura2JupiterEn(); - case AURA2AQUILA_ES: - return visitor.visitAura2AquilaEs(); - case AURA2CORA_EN: - return visitor.visitAura2CoraEn(); - case AURA2CORDELIA_EN: - return visitor.visitAura2CordeliaEn(); - case AURA2ATLAS_EN: - return visitor.visitAura2AtlasEn(); + case FLUX_KAI_EN: + return visitor.visitFluxKaiEn(); + case FLUX_BRITTANY_EN: + return visitor.visitFluxBrittanyEn(); + case FLUX_SEAN_EN: + return visitor.visitFluxSeanEn(); case AURA2HELENA_EN: return visitor.visitAura2HelenaEn(); case FLUX_HALEY_EN: @@ -367,32 +329,16 @@ public T visit(Visitor visitor) { return visitor.visitFluxJackEn(); case AURA2DRACO_EN: return visitor.visitAura2DracoEn(); - case AURA2HYPERION_EN: - return visitor.visitAura2HyperionEn(); - case AURA2CELESTE_ES: - return visitor.visitAura2CelesteEs(); case FLUX_PRIYA_EN: return visitor.visitFluxPriyaEn(); case AURA_HELIOS_EN: return visitor.visitAuraHeliosEn(); - case AURA2PLUTO_EN: - return visitor.visitAura2PlutoEn(); case AURA2JANUS_EN: return visitor.visitAura2JanusEn(); case AURA2NESTOR_ES: return visitor.visitAura2NestorEs(); - case AURA2ARCAS_EN: - return visitor.visitAura2ArcasEn(); case AURA2ORION_EN: return visitor.visitAura2OrionEn(); - case FLUX_RENEE_EN: - return visitor.visitFluxReneeEn(); - case AURA_ATHENA_EN: - return visitor.visitAuraAthenaEn(); - case AURA2ODYSSEUS_EN: - return visitor.visitAura2OdysseusEn(); - case AURA2PANDORA_EN: - return visitor.visitAura2PandoraEn(); case AURA2MINERVA_EN: return visitor.visitAura2MinervaEn(); case AURA2ALVARO_ES: @@ -403,18 +349,100 @@ public T visit(Visitor visitor) { return visitor.visitAura2VestaEn(); case FLUX_COLE_EN: return visitor.visitFluxColeEn(); - case AURA_ASTERIA_EN: - return visitor.visitAuraAsteriaEn(); case AURA2ZEUS_EN: return visitor.visitAura2ZeusEn(); - case AURA2ELECTRA_EN: - return visitor.visitAura2ElectraEn(); case AURA2ORPHEUS_EN: return visitor.visitAura2OrpheusEn(); - case AURA2ESTRELLA_ES: - return visitor.visitAura2EstrellaEs(); case AURA2THALIA_EN: return visitor.visitAura2ThaliaEn(); + case FLUX_NAVEEN_EN: + return visitor.visitFluxNaveenEn(); + case AURA2ASTERIA_EN: + return visitor.visitAura2AsteriaEn(); + case AURA2SELENA_ES: + return visitor.visitAura2SelenaEs(); + case FLUX_MARCELO_EN: + return visitor.visitFluxMarceloEn(); + case FLUX_ELISE_EN: + return visitor.visitFluxEliseEn(); + case FLUX_ALEXIS_EN: + return visitor.visitFluxAlexisEn(); + case AURA_LUNA_EN: + return visitor.visitAuraLunaEn(); + case AURA_ORPHEUS_EN: + return visitor.visitAuraOrpheusEn(); + case AURA2AMALTHEA_EN: + return visitor.visitAura2AmaltheaEn(); + case FLUX_DONOVAN_EN: + return visitor.visitFluxDonovanEn(); + case AURA2SELENE_EN: + return visitor.visitAura2SeleneEn(); + case FLUX_TANNER_EN: + return visitor.visitFluxTannerEn(); + case FLUX_SHARON_EN: + return visitor.visitFluxSharonEn(); + case AURA2LUNA_EN: + return visitor.visitAura2LunaEn(); + case AURA2PHOEBE_EN: + return visitor.visitAura2PhoebeEn(); + case FLUX_HANNAH_EN: + return visitor.visitFluxHannahEn(); + case AURA2DIANA_ES: + return visitor.visitAura2DianaEs(); + case AURA_ORION_EN: + return visitor.visitAuraOrionEn(); + case AURA2ATHENA_EN: + return visitor.visitAura2AthenaEn(); + case AURA_ARCAS_EN: + return visitor.visitAuraArcasEn(); + case AURA2THEIA_EN: + return visitor.visitAura2TheiaEn(); + case FLUX_WADE_EN: + return visitor.visitFluxWadeEn(); + case FLUX_HEATHER_EN: + return visitor.visitFluxHeatherEn(); + case FLUX_CLIFF_EN: + return visitor.visitFluxCliffEn(); + case AURA2JUPITER_EN: + return visitor.visitAura2JupiterEn(); + case FLUX_KIT_EN: + return visitor.visitFluxKitEn(); + case AURA2AQUILA_ES: + return visitor.visitAura2AquilaEs(); + case AURA2CORA_EN: + return visitor.visitAura2CoraEn(); + case AURA2CORDELIA_EN: + return visitor.visitAura2CordeliaEn(); + case AURA2ATLAS_EN: + return visitor.visitAura2AtlasEn(); + case AURA2HYPERION_EN: + return visitor.visitAura2HyperionEn(); + case AURA2CELESTE_ES: + return visitor.visitAura2CelesteEs(); + case AURA2PLUTO_EN: + return visitor.visitAura2PlutoEn(); + case FLUX_BREE_EN: + return visitor.visitFluxBreeEn(); + case AURA2ARCAS_EN: + return visitor.visitAura2ArcasEn(); + case FLUX_COLIN_EN: + return visitor.visitFluxColinEn(); + case AURA_ATHENA_EN: + return visitor.visitAuraAthenaEn(); + case AURA2ODYSSEUS_EN: + return visitor.visitAura2OdysseusEn(); + case AURA2PANDORA_EN: + return visitor.visitAura2PandoraEn(); + case FLUX_MILES_EN: + return visitor.visitFluxMilesEn(); + case AURA_ASTERIA_EN: + return visitor.visitAuraAsteriaEn(); + case AURA2ELECTRA_EN: + return visitor.visitAura2ElectraEn(); + case FLUX_GEMMA_EN: + return visitor.visitFluxGemmaEn(); + case AURA2ESTRELLA_ES: + return visitor.visitAura2EstrellaEs(); case UNKNOWN: default: return visitor.visitUnknown(string); @@ -422,28 +450,38 @@ public T visit(Visitor visitor) { } @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static DeepgramSpeakProviderModel valueOf(String value) { + public static DeepgramModel valueOf(String value) { switch (value) { case "aura-2-sirio-es": return AURA2SIRIO_ES; case "aura-2-hera-en": return AURA2HERA_EN; - case "aura-2-asteria-en": - return AURA2ASTERIA_EN; + case "flux-kelsey-en": + return FLUX_KELSEY_EN; + case "flux-sienna-en": + return FLUX_SIENNA_EN; case "aura-2-harmonia-en": return AURA2HARMONIA_EN; case "aura-2-carina-es": return AURA2CARINA_ES; + case "flux-meena-en": + return FLUX_MEENA_EN; + case "flux-meghan-en": + return FLUX_MEGHAN_EN; case "aura-zeus-en": return AURA_ZEUS_EN; case "aura-2-hermes-en": return AURA2HERMES_EN; - case "aura-2-selena-es": - return AURA2SELENA_ES; + case "flux-conor-en": + return FLUX_CONOR_EN; case "aura-2-neptune-en": return AURA2NEPTUNE_EN; + case "flux-brooke-en": + return FLUX_BROOKE_EN; case "flux-rufus-en": return FLUX_RUFUS_EN; + case "flux-wes-en": + return FLUX_WES_EN; case "aura-2-callista-en": return AURA2CALLISTA_EN; case "aura-2-aurora-en": @@ -454,68 +492,40 @@ public static DeepgramSpeakProviderModel valueOf(String value) { return FLUX_BRUCE_EN; case "aura-2-apollo-en": return AURA2APOLLO_EN; - case "flux-alexis-en": - return FLUX_ALEXIS_EN; - case "aura-luna-en": - return AURA_LUNA_EN; - case "aura-orpheus-en": - return AURA_ORPHEUS_EN; case "aura-2-delia-en": return AURA2DELIA_EN; case "aura-2-mars-en": return AURA2MARS_EN; - case "aura-2-amalthea-en": - return AURA2AMALTHEA_EN; case "aura-hera-en": return AURA_HERA_EN; - case "aura-2-selene-en": - return AURA2SELENE_EN; + case "flux-maeve-en": + return FLUX_MAEVE_EN; case "flux-marcus-en": return FLUX_MARCUS_EN; - case "flux-sharon-en": - return FLUX_SHARON_EN; case "aura-2-aries-en": return AURA2ARIES_EN; + case "flux-paige-en": + return FLUX_PAIGE_EN; case "aura-2-juno-en": return AURA2JUNO_EN; - case "aura-2-luna-en": - return AURA2LUNA_EN; case "flux-drew-en": return FLUX_DREW_EN; case "aura-2-javier-es": return AURA2JAVIER_ES; - case "aura-2-phoebe-en": - return AURA2PHOEBE_EN; - case "aura-2-diana-es": - return AURA2DIANA_ES; - case "aura-orion-en": - return AURA_ORION_EN; case "aura-2-andromeda-en": return AURA2ANDROMEDA_EN; - case "aura-2-athena-en": - return AURA2ATHENA_EN; case "aura-2-saturn-en": return AURA2SATURN_EN; - case "aura-arcas-en": - return AURA_ARCAS_EN; - case "aura-2-theia-en": - return AURA2THEIA_EN; case "aura-2-iris-en": return AURA2IRIS_EN; case "aura-angus-en": return AURA_ANGUS_EN; - case "flux-heather-en": - return FLUX_HEATHER_EN; - case "aura-2-jupiter-en": - return AURA2JUPITER_EN; - case "aura-2-aquila-es": - return AURA2AQUILA_ES; - case "aura-2-cora-en": - return AURA2CORA_EN; - case "aura-2-cordelia-en": - return AURA2CORDELIA_EN; - case "aura-2-atlas-en": - return AURA2ATLAS_EN; + case "flux-kai-en": + return FLUX_KAI_EN; + case "flux-brittany-en": + return FLUX_BRITTANY_EN; + case "flux-sean-en": + return FLUX_SEAN_EN; case "aura-2-helena-en": return AURA2HELENA_EN; case "flux-haley-en": @@ -526,32 +536,16 @@ public static DeepgramSpeakProviderModel valueOf(String value) { return FLUX_JACK_EN; case "aura-2-draco-en": return AURA2DRACO_EN; - case "aura-2-hyperion-en": - return AURA2HYPERION_EN; - case "aura-2-celeste-es": - return AURA2CELESTE_ES; case "flux-priya-en": return FLUX_PRIYA_EN; case "aura-helios-en": return AURA_HELIOS_EN; - case "aura-2-pluto-en": - return AURA2PLUTO_EN; case "aura-2-janus-en": return AURA2JANUS_EN; case "aura-2-nestor-es": return AURA2NESTOR_ES; - case "aura-2-arcas-en": - return AURA2ARCAS_EN; case "aura-2-orion-en": return AURA2ORION_EN; - case "flux-renee-en": - return FLUX_RENEE_EN; - case "aura-athena-en": - return AURA_ATHENA_EN; - case "aura-2-odysseus-en": - return AURA2ODYSSEUS_EN; - case "aura-2-pandora-en": - return AURA2PANDORA_EN; case "aura-2-minerva-en": return AURA2MINERVA_EN; case "aura-2-alvaro-es": @@ -562,20 +556,102 @@ public static DeepgramSpeakProviderModel valueOf(String value) { return AURA2VESTA_EN; case "flux-cole-en": return FLUX_COLE_EN; - case "aura-asteria-en": - return AURA_ASTERIA_EN; case "aura-2-zeus-en": return AURA2ZEUS_EN; - case "aura-2-electra-en": - return AURA2ELECTRA_EN; case "aura-2-orpheus-en": return AURA2ORPHEUS_EN; - case "aura-2-estrella-es": - return AURA2ESTRELLA_ES; case "aura-2-thalia-en": return AURA2THALIA_EN; + case "flux-naveen-en": + return FLUX_NAVEEN_EN; + case "aura-2-asteria-en": + return AURA2ASTERIA_EN; + case "aura-2-selena-es": + return AURA2SELENA_ES; + case "flux-marcelo-en": + return FLUX_MARCELO_EN; + case "flux-elise-en": + return FLUX_ELISE_EN; + case "flux-alexis-en": + return FLUX_ALEXIS_EN; + case "aura-luna-en": + return AURA_LUNA_EN; + case "aura-orpheus-en": + return AURA_ORPHEUS_EN; + case "aura-2-amalthea-en": + return AURA2AMALTHEA_EN; + case "flux-donovan-en": + return FLUX_DONOVAN_EN; + case "aura-2-selene-en": + return AURA2SELENE_EN; + case "flux-tanner-en": + return FLUX_TANNER_EN; + case "flux-sharon-en": + return FLUX_SHARON_EN; + case "aura-2-luna-en": + return AURA2LUNA_EN; + case "aura-2-phoebe-en": + return AURA2PHOEBE_EN; + case "flux-hannah-en": + return FLUX_HANNAH_EN; + case "aura-2-diana-es": + return AURA2DIANA_ES; + case "aura-orion-en": + return AURA_ORION_EN; + case "aura-2-athena-en": + return AURA2ATHENA_EN; + case "aura-arcas-en": + return AURA_ARCAS_EN; + case "aura-2-theia-en": + return AURA2THEIA_EN; + case "flux-wade-en": + return FLUX_WADE_EN; + case "flux-heather-en": + return FLUX_HEATHER_EN; + case "flux-cliff-en": + return FLUX_CLIFF_EN; + case "aura-2-jupiter-en": + return AURA2JUPITER_EN; + case "flux-kit-en": + return FLUX_KIT_EN; + case "aura-2-aquila-es": + return AURA2AQUILA_ES; + case "aura-2-cora-en": + return AURA2CORA_EN; + case "aura-2-cordelia-en": + return AURA2CORDELIA_EN; + case "aura-2-atlas-en": + return AURA2ATLAS_EN; + case "aura-2-hyperion-en": + return AURA2HYPERION_EN; + case "aura-2-celeste-es": + return AURA2CELESTE_ES; + case "aura-2-pluto-en": + return AURA2PLUTO_EN; + case "flux-bree-en": + return FLUX_BREE_EN; + case "aura-2-arcas-en": + return AURA2ARCAS_EN; + case "flux-colin-en": + return FLUX_COLIN_EN; + case "aura-athena-en": + return AURA_ATHENA_EN; + case "aura-2-odysseus-en": + return AURA2ODYSSEUS_EN; + case "aura-2-pandora-en": + return AURA2PANDORA_EN; + case "flux-miles-en": + return FLUX_MILES_EN; + case "aura-asteria-en": + return AURA_ASTERIA_EN; + case "aura-2-electra-en": + return AURA2ELECTRA_EN; + case "flux-gemma-en": + return FLUX_GEMMA_EN; + case "aura-2-estrella-es": + return AURA2ESTRELLA_ES; default: - return new DeepgramSpeakProviderModel(Value.UNKNOWN, value); + return new DeepgramModel(Value.UNKNOWN, value); } } @@ -706,30 +782,78 @@ public enum Value { AURA2JAVIER_ES, - FLUX_HALEY_EN, + FLUX_ALEXIS_EN, - FLUX_HEATHER_EN, + FLUX_BREE_EN, + + FLUX_BRITTANY_EN, + + FLUX_BROOKE_EN, + + FLUX_BRUCE_EN, + + FLUX_CLIFF_EN, FLUX_COLE_EN, - FLUX_ALEXIS_EN, + FLUX_COLIN_EN, - FLUX_PRIYA_EN, + FLUX_CONOR_EN, + + FLUX_DONOVAN_EN, + + FLUX_DREW_EN, + + FLUX_ELISE_EN, + + FLUX_GEMMA_EN, + + FLUX_HALEY_EN, + + FLUX_HANNAH_EN, + + FLUX_HEATHER_EN, FLUX_JACK_EN, - FLUX_BRUCE_EN, + FLUX_KAI_EN, - FLUX_RUFUS_EN, + FLUX_KELSEY_EN, - FLUX_DREW_EN, + FLUX_KIT_EN, + + FLUX_MAEVE_EN, - FLUX_RENEE_EN, + FLUX_MARCELO_EN, FLUX_MARCUS_EN, + FLUX_MEENA_EN, + + FLUX_MEGHAN_EN, + + FLUX_MILES_EN, + + FLUX_NAVEEN_EN, + + FLUX_PAIGE_EN, + + FLUX_PRIYA_EN, + + FLUX_RUFUS_EN, + + FLUX_SEAN_EN, + FLUX_SHARON_EN, + FLUX_SIENNA_EN, + + FLUX_TANNER_EN, + + FLUX_WADE_EN, + + FLUX_WES_EN, + UNKNOWN } @@ -860,30 +984,78 @@ public interface Visitor { T visitAura2JavierEs(); - T visitFluxHaleyEn(); + T visitFluxAlexisEn(); - T visitFluxHeatherEn(); + T visitFluxBreeEn(); + + T visitFluxBrittanyEn(); + + T visitFluxBrookeEn(); + + T visitFluxBruceEn(); + + T visitFluxCliffEn(); T visitFluxColeEn(); - T visitFluxAlexisEn(); + T visitFluxColinEn(); - T visitFluxPriyaEn(); + T visitFluxConorEn(); + + T visitFluxDonovanEn(); + + T visitFluxDrewEn(); + + T visitFluxEliseEn(); + + T visitFluxGemmaEn(); + + T visitFluxHaleyEn(); + + T visitFluxHannahEn(); + + T visitFluxHeatherEn(); T visitFluxJackEn(); - T visitFluxBruceEn(); + T visitFluxKaiEn(); - T visitFluxRufusEn(); + T visitFluxKelseyEn(); - T visitFluxDrewEn(); + T visitFluxKitEn(); - T visitFluxReneeEn(); + T visitFluxMaeveEn(); + + T visitFluxMarceloEn(); T visitFluxMarcusEn(); + T visitFluxMeenaEn(); + + T visitFluxMeghanEn(); + + T visitFluxMilesEn(); + + T visitFluxNaveenEn(); + + T visitFluxPaigeEn(); + + T visitFluxPriyaEn(); + + T visitFluxRufusEn(); + + T visitFluxSeanEn(); + T visitFluxSharonEn(); + T visitFluxSiennaEn(); + + T visitFluxTannerEn(); + + T visitFluxWadeEn(); + + T visitFluxWesEn(); + T visitUnknown(String unknownType); } } diff --git a/src/main/java/com/deepgram/types/Google.java b/src/main/java/com/deepgram/types/Google.java index 541b1e6..a720490 100644 --- a/src/main/java/com/deepgram/types/Google.java +++ b/src/main/java/com/deepgram/types/Google.java @@ -21,17 +21,17 @@ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = Google.Builder.class) public final class Google { - private final Optional version; + private final Optional version; - private final GoogleThinkProviderModel model; + private final GoogleModel model; private final Optional temperature; private final Map additionalProperties; private Google( - Optional version, - GoogleThinkProviderModel model, + Optional version, + GoogleModel model, Optional temperature, Map additionalProperties) { this.version = version; @@ -49,7 +49,7 @@ public String getType() { * @return The Google API used for the request: ai-studio-v1beta for the AI Studio API, or gemini-enterprise-agent-v1 for the Gemini Enterprise Agent (GEA) API. v1beta is accepted as an alias for ai-studio-v1beta. Defaults based on the Deepgram Voice Agent endpoint you connect to. */ @JsonProperty("version") - public Optional getVersion() { + public Optional getVersion() { return version; } @@ -57,7 +57,7 @@ public Optional getVersion() { * @return Google model to use */ @JsonProperty("model") - public GoogleThinkProviderModel getModel() { + public GoogleModel getModel() { return model; } @@ -102,7 +102,7 @@ public interface ModelStage { /** *

Google model to use

*/ - _FinalStage model(@NotNull GoogleThinkProviderModel model); + _FinalStage model(@NotNull GoogleModel model); Builder from(Google other); } @@ -117,9 +117,9 @@ public interface _FinalStage { /** *

The Google API used for the request: ai-studio-v1beta for the AI Studio API, or gemini-enterprise-agent-v1 for the Gemini Enterprise Agent (GEA) API. v1beta is accepted as an alias for ai-studio-v1beta. Defaults based on the Deepgram Voice Agent endpoint you connect to.

*/ - _FinalStage version(Optional version); + _FinalStage version(Optional version); - _FinalStage version(GoogleThinkProviderVersion version); + _FinalStage version(GoogleVersion version); /** *

Google temperature (0-2)

@@ -131,11 +131,11 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements ModelStage, _FinalStage { - private GoogleThinkProviderModel model; + private GoogleModel model; private Optional temperature = Optional.empty(); - private Optional version = Optional.empty(); + private Optional version = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -156,7 +156,7 @@ public Builder from(Google other) { */ @java.lang.Override @JsonSetter("model") - public _FinalStage model(@NotNull GoogleThinkProviderModel model) { + public _FinalStage model(@NotNull GoogleModel model) { this.model = Objects.requireNonNull(model, "model must not be null"); return this; } @@ -186,7 +186,7 @@ public _FinalStage temperature(Optional temperature) { * @return Reference to {@code this} so that method calls can be chained together. */ @java.lang.Override - public _FinalStage version(GoogleThinkProviderVersion version) { + public _FinalStage version(GoogleVersion version) { this.version = Optional.ofNullable(version); return this; } @@ -196,7 +196,7 @@ public _FinalStage version(GoogleThinkProviderVersion version) { */ @java.lang.Override @JsonSetter(value = "version", nulls = Nulls.SKIP) - public _FinalStage version(Optional version) { + public _FinalStage version(Optional version) { this.version = version; return this; } diff --git a/src/main/java/com/deepgram/types/GoogleThinkProviderModel.java b/src/main/java/com/deepgram/types/GoogleModel.java similarity index 66% rename from src/main/java/com/deepgram/types/GoogleThinkProviderModel.java rename to src/main/java/com/deepgram/types/GoogleModel.java index c7d3a5e..029ac04 100644 --- a/src/main/java/com/deepgram/types/GoogleThinkProviderModel.java +++ b/src/main/java/com/deepgram/types/GoogleModel.java @@ -6,21 +6,19 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -public final class GoogleThinkProviderModel { - public static final GoogleThinkProviderModel GEMINI20FLASH_LITE = - new GoogleThinkProviderModel(Value.GEMINI20FLASH_LITE, "gemini-2.0-flash-lite"); +public final class GoogleModel { + public static final GoogleModel GEMINI20FLASH_LITE = + new GoogleModel(Value.GEMINI20FLASH_LITE, "gemini-2.0-flash-lite"); - public static final GoogleThinkProviderModel GEMINI25FLASH = - new GoogleThinkProviderModel(Value.GEMINI25FLASH, "gemini-2.5-flash"); + public static final GoogleModel GEMINI25FLASH = new GoogleModel(Value.GEMINI25FLASH, "gemini-2.5-flash"); - public static final GoogleThinkProviderModel GEMINI20FLASH = - new GoogleThinkProviderModel(Value.GEMINI20FLASH, "gemini-2.0-flash"); + public static final GoogleModel GEMINI20FLASH = new GoogleModel(Value.GEMINI20FLASH, "gemini-2.0-flash"); private final Value value; private final String string; - GoogleThinkProviderModel(Value value, String string) { + GoogleModel(Value value, String string) { this.value = value; this.string = string; } @@ -37,9 +35,7 @@ public String toString() { @java.lang.Override public boolean equals(Object other) { - return (this == other) - || (other instanceof GoogleThinkProviderModel - && this.string.equals(((GoogleThinkProviderModel) other).string)); + return (this == other) || (other instanceof GoogleModel && this.string.equals(((GoogleModel) other).string)); } @java.lang.Override @@ -62,7 +58,7 @@ public T visit(Visitor visitor) { } @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static GoogleThinkProviderModel valueOf(String value) { + public static GoogleModel valueOf(String value) { switch (value) { case "gemini-2.0-flash-lite": return GEMINI20FLASH_LITE; @@ -71,7 +67,7 @@ public static GoogleThinkProviderModel valueOf(String value) { case "gemini-2.0-flash": return GEMINI20FLASH; default: - return new GoogleThinkProviderModel(Value.UNKNOWN, value); + return new GoogleModel(Value.UNKNOWN, value); } } diff --git a/src/main/java/com/deepgram/types/GoogleThinkProviderVersion.java b/src/main/java/com/deepgram/types/GoogleVersion.java similarity index 67% rename from src/main/java/com/deepgram/types/GoogleThinkProviderVersion.java rename to src/main/java/com/deepgram/types/GoogleVersion.java index 358ad3a..0b4f459 100644 --- a/src/main/java/com/deepgram/types/GoogleThinkProviderVersion.java +++ b/src/main/java/com/deepgram/types/GoogleVersion.java @@ -6,20 +6,19 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -public final class GoogleThinkProviderVersion { - public static final GoogleThinkProviderVersion AI_STUDIO_V1BETA = - new GoogleThinkProviderVersion(Value.AI_STUDIO_V1BETA, "ai-studio-v1beta"); +public final class GoogleVersion { + public static final GoogleVersion AI_STUDIO_V1BETA = new GoogleVersion(Value.AI_STUDIO_V1BETA, "ai-studio-v1beta"); - public static final GoogleThinkProviderVersion V1BETA = new GoogleThinkProviderVersion(Value.V1BETA, "v1beta"); + public static final GoogleVersion V1BETA = new GoogleVersion(Value.V1BETA, "v1beta"); - public static final GoogleThinkProviderVersion GEMINI_ENTERPRISE_AGENT_V1 = - new GoogleThinkProviderVersion(Value.GEMINI_ENTERPRISE_AGENT_V1, "gemini-enterprise-agent-v1"); + public static final GoogleVersion GEMINI_ENTERPRISE_AGENT_V1 = + new GoogleVersion(Value.GEMINI_ENTERPRISE_AGENT_V1, "gemini-enterprise-agent-v1"); private final Value value; private final String string; - GoogleThinkProviderVersion(Value value, String string) { + GoogleVersion(Value value, String string) { this.value = value; this.string = string; } @@ -37,8 +36,7 @@ public String toString() { @java.lang.Override public boolean equals(Object other) { return (this == other) - || (other instanceof GoogleThinkProviderVersion - && this.string.equals(((GoogleThinkProviderVersion) other).string)); + || (other instanceof GoogleVersion && this.string.equals(((GoogleVersion) other).string)); } @java.lang.Override @@ -61,7 +59,7 @@ public T visit(Visitor visitor) { } @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static GoogleThinkProviderVersion valueOf(String value) { + public static GoogleVersion valueOf(String value) { switch (value) { case "ai-studio-v1beta": return AI_STUDIO_V1BETA; @@ -70,7 +68,7 @@ public static GoogleThinkProviderVersion valueOf(String value) { case "gemini-enterprise-agent-v1": return GEMINI_ENTERPRISE_AGENT_V1; default: - return new GoogleThinkProviderVersion(Value.UNKNOWN, value); + return new GoogleVersion(Value.UNKNOWN, value); } } diff --git a/src/main/java/com/deepgram/types/Groq.java b/src/main/java/com/deepgram/types/Groq.java index ebdc61a..5d933d0 100644 --- a/src/main/java/com/deepgram/types/Groq.java +++ b/src/main/java/com/deepgram/types/Groq.java @@ -24,14 +24,14 @@ public final class Groq { private final Optional temperature; - private final Optional reasoningMode; + private final Optional reasoningMode; private final Map additionalProperties; private Groq( Optional version, Optional temperature, - Optional reasoningMode, + Optional reasoningMode, Map additionalProperties) { this.version = version; this.temperature = temperature; @@ -72,7 +72,7 @@ public Optional getTemperature() { * @return OpenAI reasoning_effort */ @JsonProperty("reasoning_mode") - public Optional getReasoningMode() { + public Optional getReasoningMode() { return reasoningMode; } @@ -113,7 +113,7 @@ public static final class Builder { private Optional temperature = Optional.empty(); - private Optional reasoningMode = Optional.empty(); + private Optional reasoningMode = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -159,12 +159,12 @@ public Builder temperature(Double temperature) { *

OpenAI reasoning_effort

*/ @JsonSetter(value = "reasoning_mode", nulls = Nulls.SKIP) - public Builder reasoningMode(Optional reasoningMode) { + public Builder reasoningMode(Optional reasoningMode) { this.reasoningMode = reasoningMode; return this; } - public Builder reasoningMode(GroqThinkProviderReasoningMode reasoningMode) { + public Builder reasoningMode(GroqReasoningMode reasoningMode) { this.reasoningMode = Optional.ofNullable(reasoningMode); return this; } diff --git a/src/main/java/com/deepgram/types/GroqThinkProviderReasoningMode.java b/src/main/java/com/deepgram/types/GroqReasoningMode.java similarity index 65% rename from src/main/java/com/deepgram/types/GroqThinkProviderReasoningMode.java rename to src/main/java/com/deepgram/types/GroqReasoningMode.java index c8bd38b..0cbf12f 100644 --- a/src/main/java/com/deepgram/types/GroqThinkProviderReasoningMode.java +++ b/src/main/java/com/deepgram/types/GroqReasoningMode.java @@ -6,24 +6,22 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -public final class GroqThinkProviderReasoningMode { - public static final GroqThinkProviderReasoningMode MEDIUM = - new GroqThinkProviderReasoningMode(Value.MEDIUM, "medium"); +public final class GroqReasoningMode { + public static final GroqReasoningMode MEDIUM = new GroqReasoningMode(Value.MEDIUM, "medium"); - public static final GroqThinkProviderReasoningMode LOW = new GroqThinkProviderReasoningMode(Value.LOW, "low"); + public static final GroqReasoningMode LOW = new GroqReasoningMode(Value.LOW, "low"); - public static final GroqThinkProviderReasoningMode MINIMAL = - new GroqThinkProviderReasoningMode(Value.MINIMAL, "minimal"); + public static final GroqReasoningMode MINIMAL = new GroqReasoningMode(Value.MINIMAL, "minimal"); - public static final GroqThinkProviderReasoningMode HIGH = new GroqThinkProviderReasoningMode(Value.HIGH, "high"); + public static final GroqReasoningMode HIGH = new GroqReasoningMode(Value.HIGH, "high"); - public static final GroqThinkProviderReasoningMode NONE = new GroqThinkProviderReasoningMode(Value.NONE, "none"); + public static final GroqReasoningMode NONE = new GroqReasoningMode(Value.NONE, "none"); private final Value value; private final String string; - GroqThinkProviderReasoningMode(Value value, String string) { + GroqReasoningMode(Value value, String string) { this.value = value; this.string = string; } @@ -41,8 +39,7 @@ public String toString() { @java.lang.Override public boolean equals(Object other) { return (this == other) - || (other instanceof GroqThinkProviderReasoningMode - && this.string.equals(((GroqThinkProviderReasoningMode) other).string)); + || (other instanceof GroqReasoningMode && this.string.equals(((GroqReasoningMode) other).string)); } @java.lang.Override @@ -69,7 +66,7 @@ public T visit(Visitor visitor) { } @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public static GroqThinkProviderReasoningMode valueOf(String value) { + public static GroqReasoningMode valueOf(String value) { switch (value) { case "medium": return MEDIUM; @@ -82,7 +79,7 @@ public static GroqThinkProviderReasoningMode valueOf(String value) { case "none": return NONE; default: - return new GroqThinkProviderReasoningMode(Value.UNKNOWN, value); + return new GroqReasoningMode(Value.UNKNOWN, value); } } diff --git a/src/main/java/com/deepgram/types/ListenV1ResponseMetadata.java b/src/main/java/com/deepgram/types/ListenV1ResponseMetadata.java index 25c4a9d..53a2090 100644 --- a/src/main/java/com/deepgram/types/ListenV1ResponseMetadata.java +++ b/src/main/java/com/deepgram/types/ListenV1ResponseMetadata.java @@ -41,6 +41,8 @@ public final class ListenV1ResponseMetadata { private final Map modelInfo; + private final Optional diarizeInfo; + private final Optional summaryInfo; private final Optional sentimentInfo; @@ -62,6 +64,7 @@ private ListenV1ResponseMetadata( int channels, List models, Map modelInfo, + Optional diarizeInfo, Optional summaryInfo, Optional sentimentInfo, Optional topicsInfo, @@ -76,6 +79,7 @@ private ListenV1ResponseMetadata( this.channels = channels; this.models = models; this.modelInfo = modelInfo; + this.diarizeInfo = diarizeInfo; this.summaryInfo = summaryInfo; this.sentimentInfo = sentimentInfo; this.topicsInfo = topicsInfo; @@ -124,6 +128,14 @@ public Map getModelInfo() { return modelInfo; } + /** + * @return The diarizer that produced the speaker labels. Present only when a diarizer ran. + */ + @JsonProperty("diarize_info") + public Optional getDiarizeInfo() { + return diarizeInfo; + } + @JsonProperty("summary_info") public Optional getSummaryInfo() { return summaryInfo; @@ -169,6 +181,7 @@ private boolean equalTo(ListenV1ResponseMetadata other) { && channels == other.channels && models.equals(other.models) && modelInfo.equals(other.modelInfo) + && diarizeInfo.equals(other.diarizeInfo) && summaryInfo.equals(other.summaryInfo) && sentimentInfo.equals(other.sentimentInfo) && topicsInfo.equals(other.topicsInfo) @@ -187,6 +200,7 @@ public int hashCode() { this.channels, this.models, this.modelInfo, + this.diarizeInfo, this.summaryInfo, this.sentimentInfo, this.topicsInfo, @@ -248,6 +262,13 @@ public interface _FinalStage { _FinalStage modelInfo(String key, Object value); + /** + *

The diarizer that produced the speaker labels. Present only when a diarizer ran.

+ */ + _FinalStage diarizeInfo(Optional diarizeInfo); + + _FinalStage diarizeInfo(ListenV1ResponseMetadataDiarizeInfo diarizeInfo); + _FinalStage summaryInfo(Optional summaryInfo); _FinalStage summaryInfo(ListenV1ResponseMetadataSummaryInfo summaryInfo); @@ -292,6 +313,8 @@ public static final class Builder private Optional summaryInfo = Optional.empty(); + private Optional diarizeInfo = Optional.empty(); + private Map modelInfo = new LinkedHashMap<>(); private List models = new ArrayList<>(); @@ -313,6 +336,7 @@ public Builder from(ListenV1ResponseMetadata other) { channels(other.getChannels()); models(other.getModels()); modelInfo(other.getModelInfo()); + diarizeInfo(other.getDiarizeInfo()); summaryInfo(other.getSummaryInfo()); sentimentInfo(other.getSentimentInfo()); topicsInfo(other.getTopicsInfo()); @@ -421,6 +445,26 @@ public _FinalStage summaryInfo(Optional sum return this; } + /** + *

The diarizer that produced the speaker labels. Present only when a diarizer ran.

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

The diarizer that produced the speaker labels. Present only when a diarizer ran.

+ */ + @java.lang.Override + @JsonSetter(value = "diarize_info", nulls = Nulls.SKIP) + public _FinalStage diarizeInfo(Optional diarizeInfo) { + this.diarizeInfo = diarizeInfo; + return this; + } + @java.lang.Override public _FinalStage modelInfo(String key, Object value) { this.modelInfo.put(key, value); @@ -493,6 +537,7 @@ public ListenV1ResponseMetadata build() { channels, models, modelInfo, + diarizeInfo, summaryInfo, sentimentInfo, topicsInfo, diff --git a/src/main/java/com/deepgram/types/ListenV1ResponseMetadataDiarizeInfo.java b/src/main/java/com/deepgram/types/ListenV1ResponseMetadataDiarizeInfo.java new file mode 100644 index 0000000..0ddebbc --- /dev/null +++ b/src/main/java/com/deepgram/types/ListenV1ResponseMetadataDiarizeInfo.java @@ -0,0 +1,162 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.types; + +import com.deepgram.core.ObjectMappers; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ListenV1ResponseMetadataDiarizeInfo.Builder.class) +public final class ListenV1ResponseMetadataDiarizeInfo { + private final String modelUuid; + + private final String arch; + + private final Map additionalProperties; + + private ListenV1ResponseMetadataDiarizeInfo( + String modelUuid, String arch, Map additionalProperties) { + this.modelUuid = modelUuid; + this.arch = arch; + this.additionalProperties = additionalProperties; + } + + /** + * @return The diarizer model UUID + */ + @JsonProperty("model_uuid") + public String getModelUuid() { + return modelUuid; + } + + /** + * @return The diarizer arch, such as v1 or v2 + */ + @JsonProperty("arch") + public String getArch() { + return arch; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ListenV1ResponseMetadataDiarizeInfo + && equalTo((ListenV1ResponseMetadataDiarizeInfo) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ListenV1ResponseMetadataDiarizeInfo other) { + return modelUuid.equals(other.modelUuid) && arch.equals(other.arch); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.modelUuid, this.arch); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static ModelUuidStage builder() { + return new Builder(); + } + + public interface ModelUuidStage { + /** + *

The diarizer model UUID

+ */ + ArchStage modelUuid(@NotNull String modelUuid); + + Builder from(ListenV1ResponseMetadataDiarizeInfo other); + } + + public interface ArchStage { + /** + *

The diarizer arch, such as v1 or v2

+ */ + _FinalStage arch(@NotNull String arch); + } + + public interface _FinalStage { + ListenV1ResponseMetadataDiarizeInfo build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements ModelUuidStage, ArchStage, _FinalStage { + private String modelUuid; + + private String arch; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(ListenV1ResponseMetadataDiarizeInfo other) { + modelUuid(other.getModelUuid()); + arch(other.getArch()); + return this; + } + + /** + *

The diarizer model UUID

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

The diarizer arch, such as v1 or v2

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("arch") + public _FinalStage arch(@NotNull String arch) { + this.arch = Objects.requireNonNull(arch, "arch must not be null"); + return this; + } + + @java.lang.Override + public ListenV1ResponseMetadataDiarizeInfo build() { + return new ListenV1ResponseMetadataDiarizeInfo(modelUuid, arch, 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/types/ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem.java b/src/main/java/com/deepgram/types/ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem.java index b54fa41..d389658 100644 --- a/src/main/java/com/deepgram/types/ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem.java +++ b/src/main/java/com/deepgram/types/ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem.java @@ -28,6 +28,10 @@ public final class ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem private final Optional confidence; + private final Optional speaker; + + private final Optional speakerConfidence; + private final Map additionalProperties; private ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem( @@ -35,11 +39,15 @@ private ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem( Optional start, Optional end, Optional confidence, + Optional speaker, + Optional speakerConfidence, Map additionalProperties) { this.word = word; this.start = start; this.end = end; this.confidence = confidence; + this.speaker = speaker; + this.speakerConfidence = speakerConfidence; this.additionalProperties = additionalProperties; } @@ -63,6 +71,22 @@ public Optional getConfidence() { return confidence; } + /** + * @return The speaker of the word, present when diarization is enabled + */ + @JsonProperty("speaker") + public Optional getSpeaker() { + return speaker; + } + + /** + * @return Confidence in the speaker assignment. Returned only for pre-recorded diarization; not available for streaming + */ + @JsonProperty("speaker_confidence") + public Optional getSpeakerConfidence() { + return speakerConfidence; + } + @java.lang.Override public boolean equals(Object other) { if (this == other) return true; @@ -79,12 +103,14 @@ private boolean equalTo(ListenV1ResponseResultsChannelsItemAlternativesItemWords return word.equals(other.word) && start.equals(other.start) && end.equals(other.end) - && confidence.equals(other.confidence); + && confidence.equals(other.confidence) + && speaker.equals(other.speaker) + && speakerConfidence.equals(other.speakerConfidence); } @java.lang.Override public int hashCode() { - return Objects.hash(this.word, this.start, this.end, this.confidence); + return Objects.hash(this.word, this.start, this.end, this.confidence, this.speaker, this.speakerConfidence); } @java.lang.Override @@ -106,6 +132,10 @@ public static final class Builder { private Optional confidence = Optional.empty(); + private Optional speaker = Optional.empty(); + + private Optional speakerConfidence = Optional.empty(); + @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -116,6 +146,8 @@ public Builder from(ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem start(other.getStart()); end(other.getEnd()); confidence(other.getConfidence()); + speaker(other.getSpeaker()); + speakerConfidence(other.getSpeakerConfidence()); return this; } @@ -163,9 +195,37 @@ public Builder confidence(Float confidence) { return this; } + /** + *

The speaker of the word, present when diarization is enabled

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

Confidence in the speaker assignment. Returned only for pre-recorded diarization; not available for streaming

+ */ + @JsonSetter(value = "speaker_confidence", nulls = Nulls.SKIP) + public Builder speakerConfidence(Optional speakerConfidence) { + this.speakerConfidence = speakerConfidence; + return this; + } + + public Builder speakerConfidence(Float speakerConfidence) { + this.speakerConfidence = Optional.ofNullable(speakerConfidence); + return this; + } + public ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem build() { return new ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem( - word, start, end, confidence, additionalProperties); + word, start, end, confidence, speaker, speakerConfidence, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SpeakV2Speed.java b/src/main/java/com/deepgram/types/SpeakV2Speed.java new file mode 100644 index 0000000..223cd63 --- /dev/null +++ b/src/main/java/com/deepgram/types/SpeakV2Speed.java @@ -0,0 +1,132 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.deepgram.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class SpeakV2Speed { + public static final SpeakV2Speed ZERO_POINT_NINE_FIVE = new SpeakV2Speed(Value.ZERO_POINT_NINE_FIVE, "0.95"); + + public static final SpeakV2Speed ONE = new SpeakV2Speed(Value.ONE, "1.00"); + + public static final SpeakV2Speed ZERO_POINT_EIGHT_FIVE = new SpeakV2Speed(Value.ZERO_POINT_EIGHT_FIVE, "0.85"); + + public static final SpeakV2Speed ONE_POINT_ONE = new SpeakV2Speed(Value.ONE_POINT_ONE, "1.10"); + + public static final SpeakV2Speed ZERO_POINT_NINE = new SpeakV2Speed(Value.ZERO_POINT_NINE, "0.90"); + + public static final SpeakV2Speed ONE_POINT_ONE_FIVE = new SpeakV2Speed(Value.ONE_POINT_ONE_FIVE, "1.15"); + + public static final SpeakV2Speed ONE_POINT_ZERO_FIVE = new SpeakV2Speed(Value.ONE_POINT_ZERO_FIVE, "1.05"); + + private final Value value; + + private final String string; + + SpeakV2Speed(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 SpeakV2Speed && this.string.equals(((SpeakV2Speed) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case ZERO_POINT_NINE_FIVE: + return visitor.visitZeroPointNineFive(); + case ONE: + return visitor.visitOne(); + case ZERO_POINT_EIGHT_FIVE: + return visitor.visitZeroPointEightFive(); + case ONE_POINT_ONE: + return visitor.visitOnePointOne(); + case ZERO_POINT_NINE: + return visitor.visitZeroPointNine(); + case ONE_POINT_ONE_FIVE: + return visitor.visitOnePointOneFive(); + case ONE_POINT_ZERO_FIVE: + return visitor.visitOnePointZeroFive(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static SpeakV2Speed valueOf(String value) { + switch (value) { + case "0.95": + return ZERO_POINT_NINE_FIVE; + case "1.00": + return ONE; + case "0.85": + return ZERO_POINT_EIGHT_FIVE; + case "1.10": + return ONE_POINT_ONE; + case "0.90": + return ZERO_POINT_NINE; + case "1.15": + return ONE_POINT_ONE_FIVE; + case "1.05": + return ONE_POINT_ZERO_FIVE; + default: + return new SpeakV2Speed(Value.UNKNOWN, value); + } + } + + public enum Value { + ZERO_POINT_EIGHT_FIVE, + + ZERO_POINT_NINE, + + ZERO_POINT_NINE_FIVE, + + ONE, + + ONE_POINT_ZERO_FIVE, + + ONE_POINT_ONE, + + ONE_POINT_ONE_FIVE, + + UNKNOWN + } + + public interface Visitor { + T visitZeroPointEightFive(); + + T visitZeroPointNine(); + + T visitZeroPointNineFive(); + + T visitOne(); + + T visitOnePointZeroFive(); + + T visitOnePointOne(); + + T visitOnePointOneFive(); + + T visitUnknown(String unknownType); + } +} From 8aca58b9c23f83c4a6b30830d0bd894c1b103468 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 10:42:46 +0100 Subject: [PATCH 04/12] chore: re-apply manual patches after regen Fern generator 4.16.0 -> 4.18.0 (CLI 5.44.6 -> 5.95.1). All five patch groups were re-applied verbatim: 4.18.0 fixed none of them. - core/ClientOptions: restored the hardcoded SDK version literals and their // x-release-please-version markers. 4.18.0 switched to a getSdkVersion() helper reading Package.getImplementationVersion(), but build.gradle sets no jar manifest attributes, so that always resolves null and the SDK would permanently report the hardcoded "0.8.1" fallback. It also changed User-Agent from "com.deepgram:" to "com.deepgram." while leaving X-Fern-SDK-Name on the colon form, so the two headers disagreed. - core/ReconnectingWebSocketListener: restored maxRetries(0) semantics ("connect once, don't retry"), the configurable connectionTimeoutMs, and applyOptionsOverride(). The last is load-bearing: core/transport/ TransportWebSocketFactory (permanently frozen) calls it, so main source does not compile without it. - listen v1/v2 + speak v1/v2 WebSocket clients: restored multi-value query-param serialization via QueryStringMapper(arraysAsRepeats=true) and the additionalProperties escape hatch on connect(). - listen v2 + speak v2 WebSocket clients: restored the forward-compat no-op for unrecognized message types. - 11 fields-less message types: restored the manual hashCode() patch. - 3 agent listen-provider unions: restored defaultImpl = V2Value. Generator changes carried forward, not reverted: - listen v2 gained sendForceEndTurn(ListenV2ForceEndTurn), grafted onto the patched client. - New types: ListenV2ForceEndTurn, ListenV2TurnInfo, SpeakV2Speed, ListenV1ResultsMetadataDiarizeInfo, ListenV1ResponseMetadataDiarizeInfo. Breaking generator changes requiring hand-maintained call-site updates: - 9 provider types lost their context prefixes (DeepgramSpeakProviderModel -> DeepgramModel, AnthropicThinkProviderModel -> AnthropicModel, GoogleThinkProviderModel/Version -> GoogleModel/GoogleVersion, CartesiaSpeakProviderModelId/Voice -> CartesiaModelId/CartesiaVoice, GroqThinkProviderReasoningMode -> GroqReasoningMode, AgentV1History* -> ConversationHistoryMessage*). Updated RegenTypesTest and the two agent examples. - speak v2 "speed" changed from a free double to the closed SpeakV2Speed enum (7 values, 0.85-1.15, serialized as a string). Updated SpeakV2ConnectWireTest. .fernignore is back to its pre-regen state (all 20 paths restored, no .bak entries) and every .bak file is deleted. Verified: ./gradlew test compileExamples -> 112 tests, 0 failures, 1 skipped (pre-existing). --- .fernignore | 40 +- examples/agent/CustomProviders.java | 8 +- examples/agent/ProviderCombinations.java | 12 +- .../java/com/deepgram/core/ClientOptions.java | 9 +- .../com/deepgram/core/ClientOptions.java.bak | 308 --------- .../core/ReconnectingWebSocketListener.java | 93 ++- .../ReconnectingWebSocketListener.java.bak | 545 --------------- .../agent/v1/types/AgentV1AgentAudioDone.java | 8 + .../v1/types/AgentV1AgentAudioDone.java.bak | 86 --- .../agent/v1/types/AgentV1KeepAlive.java | 8 + .../agent/v1/types/AgentV1KeepAlive.java.bak | 86 --- .../agent/v1/types/AgentV1ListenUpdated.java | 8 + .../v1/types/AgentV1ListenUpdated.java.bak | 86 --- .../agent/v1/types/AgentV1PromptUpdated.java | 8 + .../v1/types/AgentV1PromptUpdated.java.bak | 86 --- ...tV1SettingsAgentContextListenProvider.java | 7 +- ...ettingsAgentContextListenProvider.java.bak | 229 ------- .../AgentV1SettingsAgentListenProvider.java | 7 +- ...gentV1SettingsAgentListenProvider.java.bak | 229 ------- .../v1/types/AgentV1SettingsApplied.java | 8 + .../v1/types/AgentV1SettingsApplied.java.bak | 86 --- .../agent/v1/types/AgentV1SpeakUpdated.java | 8 + .../v1/types/AgentV1SpeakUpdated.java.bak | 86 --- .../agent/v1/types/AgentV1ThinkUpdated.java | 8 + .../v1/types/AgentV1ThinkUpdated.java.bak | 86 --- .../AgentV1UpdateListenListenProvider.java | 7 +- ...AgentV1UpdateListenListenProvider.java.bak | 229 ------- .../v1/types/AgentV1UserStartedSpeaking.java | 8 + .../types/AgentV1UserStartedSpeaking.java.bak | 86 --- .../v1/websocket/V1WebSocketClient.java | 39 +- .../v1/websocket/V1WebSocketClient.java.bak | 555 ---------------- .../listen/v2/types/ListenV2CloseStream.java | 8 + .../v2/types/ListenV2CloseStream.java.bak | 86 --- .../v2/websocket/V2WebSocketClient.java | 37 +- .../v2/websocket/V2WebSocketClient.java.bak | 508 -------------- .../speak/v1/websocket/V1WebSocketClient.java | 12 + .../v1/websocket/V1WebSocketClient.java.bak | 458 ------------- .../speak/v2/types/SpeakV2Close.java | 8 + .../speak/v2/types/SpeakV2Close.java.bak | 86 --- .../speak/v2/types/SpeakV2Flush.java | 8 + .../speak/v2/types/SpeakV2Flush.java.bak | 86 --- .../speak/v2/websocket/V2WebSocketClient.java | 29 +- .../v2/websocket/V2WebSocketClient.java.bak | 629 ------------------ .../java/com/deepgram/RegenTypesTest.java | 12 +- .../com/deepgram/SpeakV2ConnectWireTest.java | 3 +- 45 files changed, 308 insertions(+), 4731 deletions(-) delete mode 100644 src/main/java/com/deepgram/core/ClientOptions.java.bak delete mode 100644 src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak delete mode 100644 src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak delete mode 100644 src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak delete mode 100644 src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak delete mode 100644 src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak delete mode 100644 src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak delete mode 100644 src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak delete mode 100644 src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak delete mode 100644 src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak diff --git a/.fernignore b/.fernignore index 3a5eb2f..70ccf7a 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 # Forward-compat patch: Fern's generated dispatcher routes any unrecognized message # type to the error handler ("Update your SDK version..."), which makes benign new @@ -32,8 +32,8 @@ src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak # NOTE: the listen v2 and speak v2 clients below also carry the streaming query-param patches # (multi-value serialization + the additionalProperties escape hatch) documented lower down — # keep them frozen until those fixes are upstreamed too. -src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak -src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak +src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java +src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java # Streaming query-param patches on the generated WS connect() builders. Two fixes: # (1) Multi-value serialization: array-valued params (listen: keyterm, keywords, replace, search, @@ -47,24 +47,24 @@ src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java. # listen v2 and speak v2 clients are frozen above; freeze the listen v1 and speak v1 clients here too # so the fixes survive regen. Re-apply after regen; unfreeze once the generator emits array params as # repeats and serializes additionalProperties on the WS connect path (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak -src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak +src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java +src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java # Manual equals/hashCode contract fix: Fern generates equals() (all instances equal) but no # hashCode() for fields-less message types, violating the Object contract. We add a consistent # hashCode() to each. Unfreeze and drop these patches once the generator emits a matching # equals/hashCode pair for fields-less types (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak -src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak -src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak +src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java +src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java +src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java # Union default-variant fix (STOPGAP). The agent listen-provider unions declare `version` as an # optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits). Fern @@ -73,9 +73,9 @@ src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.ba # {"provider":null}, silently dropping the caller's provider on the wire. Patched to # defaultImpl = V2Value on each. Guarded by AgentSettingsProviderDefaultTest. Unfreeze and drop once # the generator stops defaulting unions to the empty _UnknownValue (tracked as an upstream Fern request). -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak -src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java +src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java # Build and project configuration build.gradle diff --git a/examples/agent/CustomProviders.java b/examples/agent/CustomProviders.java index 8ce9132..a2d4ba0 100644 --- a/examples/agent/CustomProviders.java +++ b/examples/agent/CustomProviders.java @@ -7,9 +7,9 @@ import com.deepgram.resources.agent.v1.types.AgentV1SettingsAudio; import com.deepgram.resources.agent.v1.websocket.V1WebSocketClient; import com.deepgram.types.Anthropic; -import com.deepgram.types.AnthropicThinkProviderModel; +import com.deepgram.types.AnthropicModel; import com.deepgram.types.Deepgram; -import com.deepgram.types.DeepgramSpeakProviderModel; +import com.deepgram.types.DeepgramModel; import com.deepgram.types.SpeakSettingsV1; import com.deepgram.types.SpeakSettingsV1Provider; import com.deepgram.types.ThinkSettingsV1; @@ -58,12 +58,12 @@ public static void main(String[] args) { try { // Configure Anthropic as the think provider Anthropic anthropicProvider = Anthropic.builder() - .model(AnthropicThinkProviderModel.CLAUDE_SONNET420250514) + .model(AnthropicModel.CLAUDE_SONNET420250514) .build(); // Configure Deepgram as the speak provider Deepgram deepgramSpeakProvider = Deepgram.builder() - .model(DeepgramSpeakProviderModel.AURA2ASTERIA_EN) + .model(DeepgramModel.AURA2ASTERIA_EN) .build(); AgentV1SettingsAgentContextSpeak speakSettings = diff --git a/examples/agent/ProviderCombinations.java b/examples/agent/ProviderCombinations.java index 94d5491..31b9af7 100644 --- a/examples/agent/ProviderCombinations.java +++ b/examples/agent/ProviderCombinations.java @@ -3,11 +3,11 @@ import com.deepgram.resources.agent.v1.types.AgentV1SettingsAgentContextSpeak; import com.deepgram.resources.agent.v1.types.AgentV1SettingsAgentContextThink; import com.deepgram.types.Anthropic; -import com.deepgram.types.AnthropicThinkProviderModel; +import com.deepgram.types.AnthropicModel; import com.deepgram.types.Deepgram; -import com.deepgram.types.DeepgramSpeakProviderModel; +import com.deepgram.types.DeepgramModel; import com.deepgram.types.Google; -import com.deepgram.types.GoogleThinkProviderModel; +import com.deepgram.types.GoogleModel; import com.deepgram.types.OpenAiThinkProvider; import com.deepgram.types.OpenAiThinkProviderModel; import com.deepgram.types.SpeakSettingsV1; @@ -29,7 +29,7 @@ public static void main(String[] args) { // Shared speak provider (Deepgram TTS) Deepgram deepgramSpeak = Deepgram.builder() - .model(DeepgramSpeakProviderModel.AURA2ASTERIA_EN) + .model(DeepgramModel.AURA2ASTERIA_EN) .build(); AgentV1SettingsAgentContextSpeak speakSettings = AgentV1SettingsAgentContextSpeak.of(SpeakSettingsV1.builder() .provider(SpeakSettingsV1Provider.deepgram(deepgramSpeak)) @@ -57,7 +57,7 @@ public static void main(String[] args) { // Combination 2: Anthropic Claude + Deepgram System.out.println("=== Combination 2: Anthropic + Deepgram ==="); Anthropic anthropicProvider = Anthropic.builder() - .model(AnthropicThinkProviderModel.CLAUDE_SONNET420250514) + .model(AnthropicModel.CLAUDE_SONNET420250514) .build(); AgentV1SettingsAgent anthropicConfig = AgentV1SettingsAgent.of(AgentV1SettingsAgentContext.builder() @@ -76,7 +76,7 @@ public static void main(String[] args) { // Combination 3: Google Gemini + Deepgram System.out.println("=== Combination 3: Google + Deepgram ==="); Google googleProvider = - Google.builder().model(GoogleThinkProviderModel.GEMINI25FLASH).build(); + Google.builder().model(GoogleModel.GEMINI25FLASH).build(); AgentV1SettingsAgent googleConfig = AgentV1SettingsAgent.of(AgentV1SettingsAgentContext.builder() .think(AgentV1SettingsAgentContextThink.of(ThinkSettingsV1.builder() diff --git a/src/main/java/com/deepgram/core/ClientOptions.java b/src/main/java/com/deepgram/core/ClientOptions.java index ff7ba29..61be390 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -50,10 +50,10 @@ private ClientOptions( this.headers.putAll(headers); this.headers.putAll(new HashMap() { { - put("User-Agent", "com.deepgram.deepgram-java-sdk/" + getSdkVersion()); + put("User-Agent", "com.deepgram:deepgram-java-sdk/0.8.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", getSdkVersion()); + put("X-Fern-SDK-Version", "0.8.0"); // x-release-please-version } }); this.headerSuppliers = headerSuppliers; @@ -82,11 +82,6 @@ public Map headers(RequestOptions requestOptions) { return values; } - private static String getSdkVersion() { - String version = ClientOptions.class.getPackage().getImplementationVersion(); - return version != null ? version : "0.8.1"; - } - public int timeout(RequestOptions requestOptions) { if (requestOptions == null) { return this.timeout; diff --git a/src/main/java/com/deepgram/core/ClientOptions.java.bak b/src/main/java/com/deepgram/core/ClientOptions.java.bak deleted file mode 100644 index 61be390..0000000 --- a/src/main/java/com/deepgram/core/ClientOptions.java.bak +++ /dev/null @@ -1,308 +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 initialRetryDelayMillis; - - private final Optional maxRetryDelayMillis; - - private final Optional retryJitterFactor; - - private final Optional webSocketFactory; - - private final Optional logging; - - private ClientOptions( - Environment environment, - Map headers, - Map> headerSuppliers, - OkHttpClient httpClient, - int timeout, - int maxRetries, - Optional initialRetryDelayMillis, - Optional maxRetryDelayMillis, - Optional retryJitterFactor, - Optional webSocketFactory, - Optional logging) { - this.environment = environment; - this.headers = new HashMap<>(); - this.headers.putAll(headers); - this.headers.putAll(new HashMap() { - { - put("User-Agent", "com.deepgram:deepgram-java-sdk/0.8.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.8.0"); // x-release-please-version - } - }); - this.headerSuppliers = headerSuppliers; - this.httpClient = httpClient; - this.timeout = timeout; - this.maxRetries = maxRetries; - this.initialRetryDelayMillis = initialRetryDelayMillis; - this.maxRetryDelayMillis = maxRetryDelayMillis; - this.retryJitterFactor = retryJitterFactor; - this.webSocketFactory = webSocketFactory; - this.logging = logging; - } - - public Environment environment() { - return this.environment; - } - - public Map headers(RequestOptions requestOptions) { - Map values = new HashMap<>(this.headers); - headerSuppliers.forEach((key, supplier) -> { - values.put(key, supplier.get()); - }); - if (requestOptions != null) { - values.putAll(requestOptions.getHeaders()); - } - return values; - } - - public int timeout(RequestOptions requestOptions) { - if (requestOptions == null) { - return this.timeout; - } - return requestOptions.getTimeout().orElse(this.timeout); - } - - public OkHttpClient httpClient() { - return this.httpClient; - } - - public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { - if (requestOptions == null) { - return this.httpClient; - } - return this.httpClient - .newBuilder() - .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) - .connectTimeout(0, TimeUnit.SECONDS) - .writeTimeout(0, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.SECONDS) - .build(); - } - - public int maxRetries() { - return this.maxRetries; - } - - public Optional initialRetryDelayMillis() { - return this.initialRetryDelayMillis; - } - - public Optional maxRetryDelayMillis() { - return this.maxRetryDelayMillis; - } - - public Optional retryJitterFactor() { - return this.retryJitterFactor; - } - - public Optional webSocketFactory() { - return this.webSocketFactory; - } - - public Optional logging() { - return this.logging; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Environment environment; - - private final Map headers = new HashMap<>(); - - private final Map> headerSuppliers = new HashMap<>(); - - private int maxRetries = 2; - - private Optional initialRetryDelayMillis = Optional.empty(); - - private Optional maxRetryDelayMillis = Optional.empty(); - - private Optional retryJitterFactor = Optional.empty(); - - private Optional timeout = Optional.empty(); - - private OkHttpClient httpClient = null; - - private Optional logging = Optional.empty(); - - private Optional webSocketFactory = Optional.empty(); - - public Builder environment(Environment environment) { - this.environment = environment; - return this; - } - - public Builder addHeader(String key, String value) { - if (value != null) { - this.headers.put(key, value); - } - return this; - } - - public Builder addHeader(String key, Supplier value) { - this.headerSuppliers.put(key, value); - return this; - } - - /** - * Override the timeout in seconds. Defaults to 60 seconds. - */ - public Builder timeout(int timeout) { - this.timeout = Optional.of(timeout); - return this; - } - - /** - * Override the timeout in seconds. Defaults to 60 seconds. - */ - public Builder timeout(Optional timeout) { - this.timeout = timeout; - return this; - } - - /** - * Override the maximum number of retries. Defaults to 2 retries. - */ - public Builder maxRetries(int maxRetries) { - this.maxRetries = maxRetries; - return this; - } - - /** - * Override the initial delay (in milliseconds) used for exponential backoff between retries. Defaults to 1000 milliseconds. - */ - public Builder initialRetryDelayMillis(long initialRetryDelayMillis) { - this.initialRetryDelayMillis = Optional.of(initialRetryDelayMillis); - return this; - } - - /** - * Override the maximum delay (in milliseconds) between retries. Defaults to 60000 milliseconds. - */ - public Builder maxRetryDelayMillis(long maxRetryDelayMillis) { - this.maxRetryDelayMillis = Optional.of(maxRetryDelayMillis); - return this; - } - - /** - * Override the jitter factor (between 0 and 1) applied to retry delays. Defaults to 0.2. - */ - public Builder retryJitterFactor(double retryJitterFactor) { - this.retryJitterFactor = Optional.of(retryJitterFactor); - return this; - } - - 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, - this.initialRetryDelayMillis, - this.maxRetryDelayMillis, - this.retryJitterFactor)); - } - - Logger logger = Logger.from(this.logging); - httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); - httpClientBuilder.addInterceptor(new ResponseDecompressionInterceptor()); - - this.httpClient = httpClientBuilder.build(); - this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); - - return new ClientOptions( - environment, - headers, - headerSuppliers, - httpClient, - this.timeout.get(), - this.maxRetries, - this.initialRetryDelayMillis, - this.maxRetryDelayMillis, - this.retryJitterFactor, - this.webSocketFactory, - this.logging); - } - - /** - * Create a new Builder initialized with values from an existing ClientOptions - */ - public static Builder from(ClientOptions clientOptions) { - Builder builder = new Builder(); - builder.environment = clientOptions.environment(); - builder.timeout = Optional.of(clientOptions.timeout(null)); - builder.httpClient = clientOptions.httpClient(); - builder.headers.putAll(clientOptions.headers); - builder.headerSuppliers.putAll(clientOptions.headerSuppliers); - builder.maxRetries = clientOptions.maxRetries(); - builder.initialRetryDelayMillis = clientOptions.initialRetryDelayMillis(); - builder.maxRetryDelayMillis = clientOptions.maxRetryDelayMillis(); - builder.retryJitterFactor = clientOptions.retryJitterFactor(); - builder.logging = clientOptions.logging(); - return builder; - } - } -} diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java index 0ca455a..a533068 100644 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java @@ -25,14 +25,15 @@ * Provides production-ready resilience for WebSocket connections. */ public abstract class ReconnectingWebSocketListener extends WebSocketListener { - private final long minReconnectionDelayMs; - - private final long maxReconnectionDelayMs; - - private final double reconnectionDelayGrowFactor; - - private final int maxRetries; - + // Overridable options are held behind a single volatile reference (not five separate volatile + // fields) so {@link #applyOptionsOverride} swaps them atomically — a reader that snapshots the + // reference once sees a mutually-consistent set (e.g. getNextDelay() cannot observe a new min + // paired with an old max). Rewiring is used by {@code TransportWebSocketFactory} to honour + // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. + private volatile ReconnectOptions activeOptions; + + // maxEnqueuedMessages is fixed at construction (the queue is sized once) and intentionally not + // overridable, so it stays a separate final field rather than being read from activeOptions. private final int maxEnqueuedMessages; private final AtomicInteger retryCount = new AtomicInteger(0); @@ -61,21 +62,42 @@ public abstract class ReconnectingWebSocketListener extends WebSocketListener { */ public ReconnectingWebSocketListener( ReconnectingWebSocketListener.ReconnectOptions options, Supplier connectionSupplier) { - this.minReconnectionDelayMs = options.minReconnectionDelayMs; - this.maxReconnectionDelayMs = options.maxReconnectionDelayMs; - this.reconnectionDelayGrowFactor = options.reconnectionDelayGrowFactor; - this.maxRetries = options.maxRetries; + this.activeOptions = options; this.maxEnqueuedMessages = options.maxEnqueuedMessages; this.connectionSupplier = connectionSupplier; } + /** + * 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: the override is a single atomic write to a {@code volatile} reference, so a + * reader that snapshots {@link #activeOptions} once sees a mutually-consistent set of values + * (never a new min paired with an old max). 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.activeOptions = options; + } + /** * Initiates a WebSocket connection with automatic reconnection enabled. * * Connection behavior: - * - Times out after 4000 milliseconds + * - Times out after {@code ReconnectOptions.connectionTimeoutMs} (default 4000ms) * - Thread-safe via atomic lock (returns immediately if connection in progress) - * - Retry count not incremented for initial connection attempt + * - {@code maxRetries} counts retries only — 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,20 +108,26 @@ public void connect() { if (!connectLock.compareAndSet(false, true)) { return; } - if (retryCount.get() >= maxRetries) { + // Snapshot the overridable options once so the gate and timeout below use a consistent set. + ReconnectOptions opts = this.activeOptions; + // 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() > opts.maxRetries) { connectLock.set(false); return; } try { CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); try { - webSocket = connectionFuture.get(4000, MILLISECONDS); + webSocket = connectionFuture.get(opts.connectionTimeoutMs, MILLISECONDS); } catch (TimeoutException e) { connectionFuture.cancel(true); TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + 4000 + " milliseconds" + new TimeoutException("WebSocket connection timeout after " + opts.connectionTimeoutMs + + " milliseconds" + (retryCount.get() > 0 - ? " (retry attempt #" + retryCount.get() + ? " (retry attempt #" + retryCount.get() + ")" : " (initial connection attempt)")); onWebSocketFailure(null, timeoutError, null); if (shouldReconnect.get()) { @@ -314,11 +342,14 @@ public void onClosed(WebSocket webSocket, int code, String reason) { * - 2+ = exponential backoff up to maxReconnectionDelayMs */ private long getNextDelay() { + // Single volatile read → a consistent (min, growFactor, max) snapshot even if + // applyOptionsOverride swaps the options concurrently. + ReconnectOptions opts = this.activeOptions; if (retryCount.get() == 1) { - return minReconnectionDelayMs; + return opts.minReconnectionDelayMs; } - long delay = (long) (minReconnectionDelayMs * Math.pow(reconnectionDelayGrowFactor, retryCount.get() - 1)); - return Math.min(delay, maxReconnectionDelayMs); + long delay = (long) (opts.minReconnectionDelayMs * Math.pow(opts.reconnectionDelayGrowFactor, retryCount.get() - 1)); + return Math.min(delay, opts.maxReconnectionDelayMs); } /** @@ -399,12 +430,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 +456,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 +492,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 +510,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 +535,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 a533068..0000000 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java.bak +++ /dev/null @@ -1,545 +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 { - // Overridable options are held behind a single volatile reference (not five separate volatile - // fields) so {@link #applyOptionsOverride} swaps them atomically — a reader that snapshots the - // reference once sees a mutually-consistent set (e.g. getNextDelay() cannot observe a new min - // paired with an old max). Rewiring is used by {@code TransportWebSocketFactory} to honour - // {@code DeepgramTransportFactory.reconnectOptions()} without editing the generated WS clients. - private volatile ReconnectOptions activeOptions; - - // maxEnqueuedMessages is fixed at construction (the queue is sized once) and intentionally not - // overridable, so it stays a separate final field rather than being read from activeOptions. - private final int maxEnqueuedMessages; - - private final AtomicInteger retryCount = new AtomicInteger(0); - - private final AtomicBoolean connectLock = new AtomicBoolean(false); - - private final AtomicBoolean shouldReconnect = new AtomicBoolean(true); - - protected volatile WebSocket webSocket; - - private volatile long connectionEstablishedTime = 0L; - - private final ConcurrentLinkedQueue messageQueue = new ConcurrentLinkedQueue<>(); - - private final ConcurrentLinkedQueue binaryMessageQueue = new ConcurrentLinkedQueue<>(); - - private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(); - - private final Supplier connectionSupplier; - - /** - * Creates a new reconnecting WebSocket listener. - * - * @param options Reconnection configuration options - * @param connectionSupplier Supplier that creates new WebSocket connections - */ - public ReconnectingWebSocketListener( - ReconnectingWebSocketListener.ReconnectOptions options, Supplier connectionSupplier) { - this.activeOptions = options; - this.maxEnqueuedMessages = options.maxEnqueuedMessages; - this.connectionSupplier = connectionSupplier; - } - - /** - * Replaces 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: the override is a single atomic write to a {@code volatile} reference, so a - * reader that snapshots {@link #activeOptions} once sees a mutually-consistent set of values - * (never a new min paired with an old max). 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.activeOptions = options; - } - - /** - * Initiates a WebSocket connection with automatic reconnection enabled. - * - * Connection behavior: - * - Times out after {@code ReconnectOptions.connectionTimeoutMs} (default 4000ms) - * - Thread-safe via atomic lock (returns immediately if connection in progress) - * - {@code maxRetries} counts retries only — 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; - } - // Snapshot the overridable options once so the gate and timeout below use a consistent set. - ReconnectOptions opts = this.activeOptions; - // 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() > opts.maxRetries) { - connectLock.set(false); - return; - } - try { - CompletableFuture connectionFuture = CompletableFuture.supplyAsync(connectionSupplier); - try { - webSocket = connectionFuture.get(opts.connectionTimeoutMs, MILLISECONDS); - } catch (TimeoutException e) { - connectionFuture.cancel(true); - TimeoutException timeoutError = - new TimeoutException("WebSocket connection timeout after " + opts.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() { - // Single volatile read → a consistent (min, growFactor, max) snapshot even if - // applyOptionsOverride swaps the options concurrently. - ReconnectOptions opts = this.activeOptions; - if (retryCount.get() == 1) { - return opts.minReconnectionDelayMs; - } - long delay = (long) (opts.minReconnectionDelayMs * Math.pow(opts.reconnectionDelayGrowFactor, retryCount.get() - 1)); - return Math.min(delay, opts.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); - } - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java index 71825ed..41af522 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof AgentV1AgentAudioDone; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak deleted file mode 100644 index 41af522..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java.bak +++ /dev/null @@ -1,86 +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.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1AgentAudioDone.Builder.class) -public final class AgentV1AgentAudioDone { - private final Map additionalProperties; - - private AgentV1AgentAudioDone(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier indicating the agent has finished sending audio - */ - @JsonProperty("type") - public String getType() { - return "AgentAudioDone"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1AgentAudioDone; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1AgentAudioDone other) { - return this; - } - - public AgentV1AgentAudioDone build() { - return new AgentV1AgentAudioDone(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java index 7842266..083578b 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof AgentV1KeepAlive; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak deleted file mode 100644 index 083578b..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java.bak +++ /dev/null @@ -1,86 +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.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1KeepAlive.Builder.class) -public final class AgentV1KeepAlive { - private final Map additionalProperties; - - private AgentV1KeepAlive(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier - */ - @JsonProperty("type") - public String getType() { - return "KeepAlive"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1KeepAlive; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1KeepAlive other) { - return this; - } - - public AgentV1KeepAlive build() { - return new AgentV1KeepAlive(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java index 65878c9..6018473 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof AgentV1ListenUpdated; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak deleted file mode 100644 index 6018473..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java.bak +++ /dev/null @@ -1,86 +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.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1ListenUpdated.Builder.class) -public final class AgentV1ListenUpdated { - private final Map additionalProperties; - - private AgentV1ListenUpdated(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for listen update confirmation - */ - @JsonProperty("type") - public String getType() { - return "ListenUpdated"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1ListenUpdated; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1ListenUpdated other) { - return this; - } - - public AgentV1ListenUpdated build() { - return new AgentV1ListenUpdated(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java index 68d71cc..6edec37 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof AgentV1PromptUpdated; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak deleted file mode 100644 index 6edec37..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java.bak +++ /dev/null @@ -1,86 +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.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1PromptUpdated.Builder.class) -public final class AgentV1PromptUpdated { - private final Map additionalProperties; - - private AgentV1PromptUpdated(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for prompt update confirmation - */ - @JsonProperty("type") - public String getType() { - return "PromptUpdated"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1PromptUpdated; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1PromptUpdated other) { - return this; - } - - public AgentV1PromptUpdated build() { - return new AgentV1PromptUpdated(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java index eea1585..c4ee1de 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java @@ -99,7 +99,12 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) + // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an + // empty body, so a provider payload omitting the optional "version" discriminator (exactly what + // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes + // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop + // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak deleted file mode 100644 index c4ee1de..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java.bak +++ /dev/null @@ -1,229 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -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; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Objects; -import java.util.Optional; - -public final class AgentV1SettingsAgentContextListenProvider { - private final Value value; - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - private AgentV1SettingsAgentContextListenProvider(Value value) { - this.value = value; - } - - public T visit(Visitor visitor) { - return value.visit(visitor); - } - - public static AgentV1SettingsAgentContextListenProvider v1(DeepgramListenProviderV1 value) { - return new AgentV1SettingsAgentContextListenProvider(new V1Value(value)); - } - - public static AgentV1SettingsAgentContextListenProvider v2(DeepgramListenProviderV2 value) { - return new AgentV1SettingsAgentContextListenProvider(new V2Value(value)); - } - - public boolean isV1() { - return value instanceof V1Value; - } - - public boolean isV2() { - return value instanceof V2Value; - } - - public boolean _isUnknown() { - return value instanceof _UnknownValue; - } - - public Optional getV1() { - if (isV1()) { - return Optional.of(((V1Value) value).value); - } - return Optional.empty(); - } - - public Optional getV2() { - if (isV2()) { - return Optional.of(((V2Value) value).value); - } - return Optional.empty(); - } - - public Optional _getUnknown() { - if (_isUnknown()) { - return Optional.of(((_UnknownValue) value).value); - } - return Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SettingsAgentContextListenProvider - && value.equals(((AgentV1SettingsAgentContextListenProvider) other).value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return value.toString(); - } - - @JsonValue - private Value getValue() { - return this.value; - } - - public interface Visitor { - T visitV1(DeepgramListenProviderV1 v1); - - T visitV2(DeepgramListenProviderV2 v2); - - T _visitUnknown(Object unknownType); - } - - // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an - // empty body, so a provider payload omitting the optional "version" discriminator (exactly what - // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes - // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop - // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) - @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) - @JsonIgnoreProperties(ignoreUnknown = true) - private interface Value { - T visit(Visitor visitor); - } - - @JsonTypeName("v1") - @JsonIgnoreProperties("version") - private static final class V1Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV1 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V1Value() {} - - private V1Value(DeepgramListenProviderV1 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV1(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V1Value && equalTo((V1Value) other); - } - - private boolean equalTo(V1Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentContextListenProvider{" + "value: " + value + "}"; - } - } - - @JsonTypeName("v2") - @JsonIgnoreProperties("version") - private static final class V2Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV2 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V2Value() {} - - private V2Value(DeepgramListenProviderV2 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV2(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V2Value && equalTo((V2Value) other); - } - - private boolean equalTo(V2Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentContextListenProvider{" + "value: " + value + "}"; - } - } - - @JsonIgnoreProperties("version") - private static final class _UnknownValue implements Value { - private String type; - - @JsonValue - private Object value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private _UnknownValue(@JsonProperty("value") Object value) {} - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor._visitUnknown(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof _UnknownValue && equalTo((_UnknownValue) other); - } - - private boolean equalTo(_UnknownValue other) { - return type.equals(other.type) && value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.type, this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentContextListenProvider{" + "type: " + type + ", value: " + value + "}"; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java index 4309ec0..3bf0333 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java @@ -99,7 +99,12 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) + // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an + // empty body, so a provider payload omitting the optional "version" discriminator (exactly what + // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes + // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop + // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak deleted file mode 100644 index 3bf0333..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java.bak +++ /dev/null @@ -1,229 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -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; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Objects; -import java.util.Optional; - -public final class AgentV1SettingsAgentListenProvider { - private final Value value; - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - private AgentV1SettingsAgentListenProvider(Value value) { - this.value = value; - } - - public T visit(Visitor visitor) { - return value.visit(visitor); - } - - public static AgentV1SettingsAgentListenProvider v1(DeepgramListenProviderV1 value) { - return new AgentV1SettingsAgentListenProvider(new V1Value(value)); - } - - public static AgentV1SettingsAgentListenProvider v2(DeepgramListenProviderV2 value) { - return new AgentV1SettingsAgentListenProvider(new V2Value(value)); - } - - public boolean isV1() { - return value instanceof V1Value; - } - - public boolean isV2() { - return value instanceof V2Value; - } - - public boolean _isUnknown() { - return value instanceof _UnknownValue; - } - - public Optional getV1() { - if (isV1()) { - return Optional.of(((V1Value) value).value); - } - return Optional.empty(); - } - - public Optional getV2() { - if (isV2()) { - return Optional.of(((V2Value) value).value); - } - return Optional.empty(); - } - - public Optional _getUnknown() { - if (_isUnknown()) { - return Optional.of(((_UnknownValue) value).value); - } - return Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SettingsAgentListenProvider - && value.equals(((AgentV1SettingsAgentListenProvider) other).value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return value.toString(); - } - - @JsonValue - private Value getValue() { - return this.value; - } - - public interface Visitor { - T visitV1(DeepgramListenProviderV1 v1); - - T visitV2(DeepgramListenProviderV2 v2); - - T _visitUnknown(Object unknownType); - } - - // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an - // empty body, so a provider payload omitting the optional "version" discriminator (exactly what - // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes - // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop - // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) - @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) - @JsonIgnoreProperties(ignoreUnknown = true) - private interface Value { - T visit(Visitor visitor); - } - - @JsonTypeName("v1") - @JsonIgnoreProperties("version") - private static final class V1Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV1 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V1Value() {} - - private V1Value(DeepgramListenProviderV1 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV1(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V1Value && equalTo((V1Value) other); - } - - private boolean equalTo(V1Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentListenProvider{" + "value: " + value + "}"; - } - } - - @JsonTypeName("v2") - @JsonIgnoreProperties("version") - private static final class V2Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV2 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V2Value() {} - - private V2Value(DeepgramListenProviderV2 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV2(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V2Value && equalTo((V2Value) other); - } - - private boolean equalTo(V2Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentListenProvider{" + "value: " + value + "}"; - } - } - - @JsonIgnoreProperties("version") - private static final class _UnknownValue implements Value { - private String type; - - @JsonValue - private Object value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private _UnknownValue(@JsonProperty("value") Object value) {} - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor._visitUnknown(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof _UnknownValue && equalTo((_UnknownValue) other); - } - - private boolean equalTo(_UnknownValue other) { - return type.equals(other.type) && value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.type, this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1SettingsAgentListenProvider{" + "type: " + type + ", value: " + value + "}"; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java index 0d60b72..fb23c60 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof AgentV1SettingsApplied; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak deleted file mode 100644 index fb23c60..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java.bak +++ /dev/null @@ -1,86 +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.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1SettingsApplied.Builder.class) -public final class AgentV1SettingsApplied { - private final Map additionalProperties; - - private AgentV1SettingsApplied(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for settings applied confirmation - */ - @JsonProperty("type") - public String getType() { - return "SettingsApplied"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SettingsApplied; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1SettingsApplied other) { - return this; - } - - public AgentV1SettingsApplied build() { - return new AgentV1SettingsApplied(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java index 747aa9d..7619157 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof AgentV1SpeakUpdated; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak deleted file mode 100644 index 7619157..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java.bak +++ /dev/null @@ -1,86 +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.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1SpeakUpdated.Builder.class) -public final class AgentV1SpeakUpdated { - private final Map additionalProperties; - - private AgentV1SpeakUpdated(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for speak update confirmation - */ - @JsonProperty("type") - public String getType() { - return "SpeakUpdated"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1SpeakUpdated; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1SpeakUpdated other) { - return this; - } - - public AgentV1SpeakUpdated build() { - return new AgentV1SpeakUpdated(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java index 5668a71..2f2a981 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof AgentV1ThinkUpdated; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak deleted file mode 100644 index 2f2a981..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java.bak +++ /dev/null @@ -1,86 +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.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1ThinkUpdated.Builder.class) -public final class AgentV1ThinkUpdated { - private final Map additionalProperties; - - private AgentV1ThinkUpdated(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier for think update confirmation - */ - @JsonProperty("type") - public String getType() { - return "ThinkUpdated"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1ThinkUpdated; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1ThinkUpdated other) { - return this; - } - - public AgentV1ThinkUpdated build() { - return new AgentV1ThinkUpdated(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java index e693a04..a0b84d9 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java @@ -99,7 +99,12 @@ public interface Visitor { T _visitUnknown(Object unknownType); } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = _UnknownValue.class) + // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an + // empty body, so a provider payload omitting the optional "version" discriminator (exactly what + // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes + // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop + // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) @JsonIgnoreProperties(ignoreUnknown = true) private interface Value { diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak deleted file mode 100644 index a0b84d9..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java.bak +++ /dev/null @@ -1,229 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -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; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.annotation.JsonValue; -import java.util.Objects; -import java.util.Optional; - -public final class AgentV1UpdateListenListenProvider { - private final Value value; - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - private AgentV1UpdateListenListenProvider(Value value) { - this.value = value; - } - - public T visit(Visitor visitor) { - return value.visit(visitor); - } - - public static AgentV1UpdateListenListenProvider v1(DeepgramListenProviderV1 value) { - return new AgentV1UpdateListenListenProvider(new V1Value(value)); - } - - public static AgentV1UpdateListenListenProvider v2(DeepgramListenProviderV2 value) { - return new AgentV1UpdateListenListenProvider(new V2Value(value)); - } - - public boolean isV1() { - return value instanceof V1Value; - } - - public boolean isV2() { - return value instanceof V2Value; - } - - public boolean _isUnknown() { - return value instanceof _UnknownValue; - } - - public Optional getV1() { - if (isV1()) { - return Optional.of(((V1Value) value).value); - } - return Optional.empty(); - } - - public Optional getV2() { - if (isV2()) { - return Optional.of(((V2Value) value).value); - } - return Optional.empty(); - } - - public Optional _getUnknown() { - if (_isUnknown()) { - return Optional.of(((_UnknownValue) value).value); - } - return Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1UpdateListenListenProvider - && value.equals(((AgentV1UpdateListenListenProvider) other).value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return value.toString(); - } - - @JsonValue - private Value getValue() { - return this.value; - } - - public interface Visitor { - T visitV1(DeepgramListenProviderV1 v1); - - T visitV2(DeepgramListenProviderV2 v2); - - T _visitUnknown(Object unknownType); - } - - // Manual patch (STOPGAP): Fern defaults this union to _UnknownValue, whose @JsonCreator has an - // empty body, so a provider payload omitting the optional "version" discriminator (exactly what - // 0.7.x emits) is silently dropped — it deserializes to an empty unknown variant and re-serializes - // as null. Default to V2Value so version-less payloads resolve to V2. Frozen in .fernignore; drop - // once the generator stops defaulting unions to the empty _UnknownValue (upstream Fern request). - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true, defaultImpl = V2Value.class) - @JsonSubTypes({@JsonSubTypes.Type(V1Value.class), @JsonSubTypes.Type(V2Value.class)}) - @JsonIgnoreProperties(ignoreUnknown = true) - private interface Value { - T visit(Visitor visitor); - } - - @JsonTypeName("v1") - @JsonIgnoreProperties("version") - private static final class V1Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV1 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V1Value() {} - - private V1Value(DeepgramListenProviderV1 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV1(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V1Value && equalTo((V1Value) other); - } - - private boolean equalTo(V1Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1UpdateListenListenProvider{" + "value: " + value + "}"; - } - } - - @JsonTypeName("v2") - @JsonIgnoreProperties("version") - private static final class V2Value implements Value { - @JsonUnwrapped - @JsonIgnoreProperties(value = "version", allowSetters = true) - private DeepgramListenProviderV2 value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private V2Value() {} - - private V2Value(DeepgramListenProviderV2 value) { - this.value = value; - } - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor.visitV2(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof V2Value && equalTo((V2Value) other); - } - - private boolean equalTo(V2Value other) { - return value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1UpdateListenListenProvider{" + "value: " + value + "}"; - } - } - - @JsonIgnoreProperties("version") - private static final class _UnknownValue implements Value { - private String type; - - @JsonValue - private Object value; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private _UnknownValue(@JsonProperty("value") Object value) {} - - @java.lang.Override - public T visit(Visitor visitor) { - return visitor._visitUnknown(value); - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof _UnknownValue && equalTo((_UnknownValue) other); - } - - private boolean equalTo(_UnknownValue other) { - return type.equals(other.type) && value.equals(other.value); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.type, this.value); - } - - @java.lang.Override - public String toString() { - return "AgentV1UpdateListenListenProvider{" + "type: " + type + ", value: " + value + "}"; - } - } -} diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java index edb80f8..7c3fc38 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof AgentV1UserStartedSpeaking; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak deleted file mode 100644 index 7c3fc38..0000000 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java.bak +++ /dev/null @@ -1,86 +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.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = AgentV1UserStartedSpeaking.Builder.class) -public final class AgentV1UserStartedSpeaking { - private final Map additionalProperties; - - private AgentV1UserStartedSpeaking(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier indicating that the user has begun speaking - */ - @JsonProperty("type") - public String getType() { - return "UserStartedSpeaking"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof AgentV1UserStartedSpeaking; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(AgentV1UserStartedSpeaking other) { - return this; - } - - public AgentV1UserStartedSpeaking build() { - return new AgentV1UserStartedSpeaking(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java index 4952808..ffa4953 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java @@ -6,6 +6,7 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -138,8 +139,12 @@ public CompletableFuture connect(V1ConnectOptions options) { "endpointing", String.valueOf(options.getEndpointing().get())); } if (options.getExtra() != null && options.getExtra().isPresent()) { - urlBuilder.addQueryParameter( - "extra", String.valueOf(options.getExtra().get())); + // Array-valued query params (String | List unions) must serialize as repeated + // params (extra=a&extra=b), not a stringified list. The generated streaming template + // uses String.valueOf(...), which mangles a List into "[a, b]"; route these through + // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. + QueryStringMapper.addQueryParameter( + urlBuilder, "extra", options.getExtra().get().get(), true); } if (options.getInterimResults() != null && options.getInterimResults().isPresent()) { urlBuilder.addQueryParameter( @@ -147,12 +152,12 @@ public CompletableFuture connect(V1ConnectOptions options) { String.valueOf(options.getInterimResults().get())); } if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - urlBuilder.addQueryParameter( - "keyterm", String.valueOf(options.getKeyterm().get())); + QueryStringMapper.addQueryParameter( + urlBuilder, "keyterm", options.getKeyterm().get().get(), true); } if (options.getKeywords() != null && options.getKeywords().isPresent()) { - urlBuilder.addQueryParameter( - "keywords", String.valueOf(options.getKeywords().get())); + QueryStringMapper.addQueryParameter( + urlBuilder, "keywords", options.getKeywords().get().get(), true); } if (options.getLanguage() != null && options.getLanguage().isPresent()) { urlBuilder.addQueryParameter( @@ -185,23 +190,24 @@ public CompletableFuture connect(V1ConnectOptions options) { "redact", String.valueOf(options.getRedact().get())); } if (options.getReplace() != null && options.getReplace().isPresent()) { - urlBuilder.addQueryParameter( - "replace", String.valueOf(options.getReplace().get())); + QueryStringMapper.addQueryParameter( + urlBuilder, "replace", options.getReplace().get().get(), true); } if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { urlBuilder.addQueryParameter( "sample_rate", String.valueOf(options.getSampleRate().get())); } if (options.getSearch() != null && options.getSearch().isPresent()) { - urlBuilder.addQueryParameter( - "search", String.valueOf(options.getSearch().get())); + QueryStringMapper.addQueryParameter( + urlBuilder, "search", options.getSearch().get().get(), true); } if (options.getSmartFormat() != null && options.getSmartFormat().isPresent()) { urlBuilder.addQueryParameter( "smart_format", String.valueOf(options.getSmartFormat().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); + QueryStringMapper.addQueryParameter( + urlBuilder, "tag", options.getTag().get().get(), true); } if (options.getUtteranceEndMs() != null && options.getUtteranceEndMs().isPresent()) { urlBuilder.addQueryParameter( @@ -216,6 +222,17 @@ public CompletableFuture connect(V1ConnectOptions options) { urlBuilder.addQueryParameter( "version", String.valueOf(options.getVersion().get())); } + // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. + // The generated template only serializes the typed options and drops these otherwise. + // ConnectOptions is request-only (never deserialized), so this map holds only what the + // caller set via the builder. Routed through QueryStringMapper to match the REST path. + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); diff --git a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak deleted file mode 100644 index ffa4953..0000000 --- a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java.bak +++ /dev/null @@ -1,555 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.listen.v1.websocket; - -import com.deepgram.core.ClientOptions; -import com.deepgram.core.DisconnectReason; -import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; -import com.deepgram.core.ReconnectingWebSocketListener; -import com.deepgram.core.RequestOptions; -import com.deepgram.core.WebSocketReadyState; -import com.deepgram.resources.listen.v1.types.ListenV1CloseStream; -import com.deepgram.resources.listen.v1.types.ListenV1Finalize; -import com.deepgram.resources.listen.v1.types.ListenV1KeepAlive; -import com.deepgram.resources.listen.v1.types.ListenV1Metadata; -import com.deepgram.resources.listen.v1.types.ListenV1Results; -import com.deepgram.resources.listen.v1.types.ListenV1SpeechStarted; -import com.deepgram.resources.listen.v1.types.ListenV1UtteranceEnd; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Consumer; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okio.ByteString; - -/** - * WebSocket client for the v1 channel. - * Provides real-time bidirectional communication with strongly-typed messages. - */ -public class V1WebSocketClient implements AutoCloseable { - protected final ClientOptions clientOptions; - - private final ObjectMapper objectMapper; - - private final OkHttpClient okHttpClient; - - private ScheduledExecutorService timeoutExecutor; - - private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; - - private volatile Runnable onConnectedHandler; - - private volatile Consumer onDisconnectedHandler; - - private volatile Consumer onErrorHandler; - - private volatile Consumer onMessageHandler; - - private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; - - private CompletableFuture connectionFuture; - - private ReconnectingWebSocketListener reconnectingListener; - - private volatile Consumer resultsHandler; - - private volatile Consumer metadataHandler; - - private volatile Consumer utteranceEndHandler; - - private volatile Consumer speechStartedHandler; - - /** - * Creates a new async WebSocket client for the v1 channel. - */ - public V1WebSocketClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.objectMapper = ObjectMappers.JSON_MAPPER; - this.okHttpClient = clientOptions.httpClient(); - } - - /** - * Establishes the WebSocket connection asynchronously with automatic reconnection. - * @return a CompletableFuture that completes when the connection is established - * @param options connection options including query parameters - */ - public CompletableFuture connect(V1ConnectOptions options) { - connectionFuture = new CompletableFuture<>(); - String baseUrl = clientOptions.environment().getProductionURL(); - String fullPath = "/v1/listen"; - if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { - fullPath = fullPath.substring(1); - } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { - fullPath = "/" + fullPath; - } - // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing - if (baseUrl.startsWith("wss://")) { - baseUrl = "https://" + baseUrl.substring(6); - } else if (baseUrl.startsWith("ws://")) { - baseUrl = "http://" + baseUrl.substring(5); - } - HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); - if (parsedUrl == null) { - throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); - } - HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); - if (options.getCallback() != null && options.getCallback().isPresent()) { - urlBuilder.addQueryParameter( - "callback", String.valueOf(options.getCallback().get())); - } - if (options.getCallbackMethod() != null && options.getCallbackMethod().isPresent()) { - urlBuilder.addQueryParameter( - "callback_method", - String.valueOf(options.getCallbackMethod().get())); - } - if (options.getChannels() != null && options.getChannels().isPresent()) { - urlBuilder.addQueryParameter( - "channels", String.valueOf(options.getChannels().get())); - } - if (options.getDetectEntities() != null && options.getDetectEntities().isPresent()) { - urlBuilder.addQueryParameter( - "detect_entities", - String.valueOf(options.getDetectEntities().get())); - } - if (options.getDiarize() != null && options.getDiarize().isPresent()) { - urlBuilder.addQueryParameter( - "diarize", String.valueOf(options.getDiarize().get())); - } - if (options.getDiarizeModel() != null && options.getDiarizeModel().isPresent()) { - urlBuilder.addQueryParameter( - "diarize_model", String.valueOf(options.getDiarizeModel().get())); - } - if (options.getDictation() != null && options.getDictation().isPresent()) { - urlBuilder.addQueryParameter( - "dictation", String.valueOf(options.getDictation().get())); - } - if (options.getEncoding() != null && options.getEncoding().isPresent()) { - urlBuilder.addQueryParameter( - "encoding", String.valueOf(options.getEncoding().get())); - } - if (options.getEndpointing() != null && options.getEndpointing().isPresent()) { - urlBuilder.addQueryParameter( - "endpointing", String.valueOf(options.getEndpointing().get())); - } - if (options.getExtra() != null && options.getExtra().isPresent()) { - // Array-valued query params (String | List unions) must serialize as repeated - // params (extra=a&extra=b), not a stringified list. The generated streaming template - // uses String.valueOf(...), which mangles a List into "[a, b]"; route these through - // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. - QueryStringMapper.addQueryParameter( - urlBuilder, "extra", options.getExtra().get().get(), true); - } - if (options.getInterimResults() != null && options.getInterimResults().isPresent()) { - urlBuilder.addQueryParameter( - "interim_results", - String.valueOf(options.getInterimResults().get())); - } - if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "keyterm", options.getKeyterm().get().get(), true); - } - if (options.getKeywords() != null && options.getKeywords().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "keywords", options.getKeywords().get().get(), true); - } - if (options.getLanguage() != null && options.getLanguage().isPresent()) { - urlBuilder.addQueryParameter( - "language", String.valueOf(options.getLanguage().get())); - } - if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { - urlBuilder.addQueryParameter( - "mip_opt_out", String.valueOf(options.getMipOptOut().get())); - } - urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); - if (options.getMultichannel() != null && options.getMultichannel().isPresent()) { - urlBuilder.addQueryParameter( - "multichannel", String.valueOf(options.getMultichannel().get())); - } - if (options.getNumerals() != null && options.getNumerals().isPresent()) { - urlBuilder.addQueryParameter( - "numerals", String.valueOf(options.getNumerals().get())); - } - if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { - urlBuilder.addQueryParameter( - "profanity_filter", - String.valueOf(options.getProfanityFilter().get())); - } - if (options.getPunctuate() != null && options.getPunctuate().isPresent()) { - urlBuilder.addQueryParameter( - "punctuate", String.valueOf(options.getPunctuate().get())); - } - if (options.getRedact() != null && options.getRedact().isPresent()) { - urlBuilder.addQueryParameter( - "redact", String.valueOf(options.getRedact().get())); - } - if (options.getReplace() != null && options.getReplace().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "replace", options.getReplace().get().get(), true); - } - if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { - urlBuilder.addQueryParameter( - "sample_rate", String.valueOf(options.getSampleRate().get())); - } - if (options.getSearch() != null && options.getSearch().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "search", options.getSearch().get().get(), true); - } - if (options.getSmartFormat() != null && options.getSmartFormat().isPresent()) { - urlBuilder.addQueryParameter( - "smart_format", String.valueOf(options.getSmartFormat().get())); - } - if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "tag", options.getTag().get().get(), true); - } - if (options.getUtteranceEndMs() != null && options.getUtteranceEndMs().isPresent()) { - urlBuilder.addQueryParameter( - "utterance_end_ms", - String.valueOf(options.getUtteranceEndMs().get())); - } - if (options.getVadEvents() != null && options.getVadEvents().isPresent()) { - urlBuilder.addQueryParameter( - "vad_events", String.valueOf(options.getVadEvents().get())); - } - if (options.getVersion() != null && options.getVersion().isPresent()) { - urlBuilder.addQueryParameter( - "version", String.valueOf(options.getVersion().get())); - } - // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. - // The generated template only serializes the typed options and drops these otherwise. - // ConnectOptions is request-only (never deserialized), so this map holds only what the - // caller set via the builder. Routed through QueryStringMapper to match the REST path. - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } - Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); - final Request request = requestBuilder.build(); - this.readyState = WebSocketReadyState.CONNECTING; - ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null - ? this.reconnectOptions - : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); - this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { - @Override - protected void onWebSocketOpen(WebSocket webSocket, Response response) { - readyState = WebSocketReadyState.OPEN; - if (onConnectedHandler != null) { - onConnectedHandler.run(); - } - connectionFuture.complete(null); - } - - @Override - protected void onWebSocketMessage(WebSocket webSocket, String text) { - handleIncomingMessage(text); - } - - @Override - protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) {} - - @Override - protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { - readyState = WebSocketReadyState.CLOSED; - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException(t)); - } - connectionFuture.completeExceptionally(t); - } - - @Override - protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { - readyState = WebSocketReadyState.CLOSED; - if (onDisconnectedHandler != null) { - onDisconnectedHandler.accept(new DisconnectReason(code, reason)); - } - } - }; - reconnectingListener.connect(); - return connectionFuture; - } - - /** - * Disconnects the WebSocket connection and releases resources. - */ - public void disconnect() { - reconnectingListener.disconnect(); - if (timeoutExecutor != null) { - timeoutExecutor.shutdownNow(); - timeoutExecutor = null; - } - } - - /** - * Gets the current state of the WebSocket connection. - * - * This provides the actual connection state, similar to the W3C WebSocket API. - * - * @return the current WebSocket ready state - */ - public WebSocketReadyState getReadyState() { - return readyState; - } - - /** - * Sends a ListenV1Media message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendMedia(ByteString message) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - // Use reconnecting listener's sendBinary method which handles queuing - reconnectingListener.sendBinary(message); - future.complete(null); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); - } - return future; - } - - /** - * Sends a ListenV1Finalize message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendFinalize(ListenV1Finalize message) { - return sendMessage(message); - } - - /** - * Sends a ListenV1CloseStream message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendCloseStream(ListenV1CloseStream message) { - return sendMessage(message); - } - - /** - * Sends a ListenV1KeepAlive message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendKeepAlive(ListenV1KeepAlive message) { - return sendMessage(message); - } - - /** - * Registers a handler for ListenV1Results messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onResults(Consumer handler) { - this.resultsHandler = handler; - } - - /** - * Registers a handler for ListenV1Metadata messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onMetadata(Consumer handler) { - this.metadataHandler = handler; - } - - /** - * Registers a handler for ListenV1UtteranceEnd messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onUtteranceEnd(Consumer handler) { - this.utteranceEndHandler = handler; - } - - /** - * Registers a handler for ListenV1SpeechStarted messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeechStarted(Consumer handler) { - this.speechStartedHandler = handler; - } - - /** - * Registers a handler called when the connection is established. - * @param handler the handler to invoke when connected - */ - public void onConnected(Runnable handler) { - this.onConnectedHandler = handler; - } - - /** - * Registers a handler called when the connection is closed. - * @param handler the handler to invoke when disconnected - */ - public void onDisconnected(Consumer handler) { - this.onDisconnectedHandler = handler; - } - - /** - * Registers a handler called when an error occurs. - * @param handler the handler to invoke on error - */ - public void onError(Consumer handler) { - this.onErrorHandler = handler; - } - - /** - * Registers a handler called for every incoming text message. - * The handler receives the raw JSON string before type-specific dispatch. - * @param handler the handler to invoke with the raw message JSON - */ - public void onMessage(Consumer handler) { - this.onMessageHandler = handler; - } - - /** - * Configures reconnection behavior. Must be called before {@link #connect}. - * - * @param options the reconnection options (backoff, retries, queue size) - */ - public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { - this.reconnectOptions = options; - } - - /** - * Closes this WebSocket client, releasing all resources. - * Equivalent to calling {@link #disconnect()}. - */ - @Override - public void close() { - disconnect(); - } - - /** - * Ensures the WebSocket is connected and ready to send messages. - * @throws IllegalStateException if the socket is not connected or not open - */ - private void assertSocketIsOpen() { - if (reconnectingListener.getWebSocket() == null) { - throw new IllegalStateException("WebSocket is not connected. Call connect() first."); - } - if (readyState != WebSocketReadyState.OPEN) { - throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); - } - } - - private CompletableFuture sendMessage(Object body) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - String json = objectMapper.writeValueAsString(body); - // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json); - future.complete(null); - } catch (IllegalStateException e) { - future.completeExceptionally(e); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send message", e)); - } - return future; - } - - private void handleIncomingMessage(String json) { - try { - if (onMessageHandler != null) { - onMessageHandler.accept(json); - } - JsonNode node = objectMapper.readTree(json); - if (node == null || node.isNull()) { - throw new IllegalArgumentException("Received null or invalid JSON message"); - } - if (node.has("transaction_key") - && node.has("request_id") - && node.has("sha256") - && node.has("created") - && node.has("duration") - && node.has("channels") - && "Metadata".equals(node.path("type").asText())) { - ListenV1Metadata metadataHandlerEvent = null; - try { - metadataHandlerEvent = objectMapper.treeToValue(node, ListenV1Metadata.class); - } catch (Exception e) { - } - if (metadataHandlerEvent != null) { - if (metadataHandler != null) { - metadataHandler.accept(metadataHandlerEvent); - } - return; - } - } - if (node.has("channel_index") - && node.has("duration") - && node.has("start") - && node.has("channel") - && node.has("metadata") - && "Results".equals(node.path("type").asText())) { - ListenV1Results resultsHandlerEvent = null; - try { - resultsHandlerEvent = objectMapper.treeToValue(node, ListenV1Results.class); - } catch (Exception e) { - } - if (resultsHandlerEvent != null) { - if (resultsHandler != null) { - resultsHandler.accept(resultsHandlerEvent); - } - return; - } - } - if (node.has("channel") - && node.has("last_word_end") - && "UtteranceEnd".equals(node.path("type").asText())) { - ListenV1UtteranceEnd utteranceEndHandlerEvent = null; - try { - utteranceEndHandlerEvent = objectMapper.treeToValue(node, ListenV1UtteranceEnd.class); - } catch (Exception e) { - } - if (utteranceEndHandlerEvent != null) { - if (utteranceEndHandler != null) { - utteranceEndHandler.accept(utteranceEndHandlerEvent); - } - return; - } - } - if (node.has("channel") - && node.has("timestamp") - && "SpeechStarted".equals(node.path("type").asText())) { - ListenV1SpeechStarted speechStartedHandlerEvent = null; - try { - speechStartedHandlerEvent = objectMapper.treeToValue(node, ListenV1SpeechStarted.class); - } catch (Exception e) { - } - if (speechStartedHandlerEvent != null) { - if (speechStartedHandler != null) { - speechStartedHandler.accept(speechStartedHandlerEvent); - } - return; - } - } - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException( - "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) - + "... Update your SDK version to support new message types.")); - } - } catch (Exception e) { - if (onErrorHandler != null) { - onErrorHandler.accept(e); - } - } - } -} diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java index e6308e2..e508681 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof ListenV2CloseStream; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak deleted file mode 100644 index e508681..0000000 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java.bak +++ /dev/null @@ -1,86 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.listen.v2.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = ListenV2CloseStream.Builder.class) -public final class ListenV2CloseStream { - private final Map additionalProperties; - - private ListenV2CloseStream(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier - */ - @JsonProperty("type") - public String getType() { - return "CloseStream"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof ListenV2CloseStream; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(ListenV2CloseStream other) { - return this; - } - - public ListenV2CloseStream build() { - return new ListenV2CloseStream(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java index 0e1535b..3fc3dd5 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java @@ -6,6 +6,7 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -126,12 +127,16 @@ public CompletableFuture connect(V2ConnectOptions options) { "eot_timeout_ms", String.valueOf(options.getEotTimeoutMs().get())); } if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - urlBuilder.addQueryParameter( - "keyterm", String.valueOf(options.getKeyterm().get())); + // Array-valued query params (String | List unions) must serialize as repeated + // params (keyterm=a&keyterm=b), not a stringified list. The generated streaming template + // uses String.valueOf(...), which mangles a List into "[a, b]"; route these through + // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. + QueryStringMapper.addQueryParameter( + urlBuilder, "keyterm", options.getKeyterm().get().get(), true); } if (options.getLanguageHint() != null && options.getLanguageHint().isPresent()) { - urlBuilder.addQueryParameter( - "language_hint", String.valueOf(options.getLanguageHint().get())); + QueryStringMapper.addQueryParameter( + urlBuilder, "language_hint", options.getLanguageHint().get().get(), true); } if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { urlBuilder.addQueryParameter( @@ -151,7 +156,19 @@ public CompletableFuture connect(V2ConnectOptions options) { "mip_opt_out", String.valueOf(options.getMipOptOut().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); + QueryStringMapper.addQueryParameter( + urlBuilder, "tag", options.getTag().get().get(), true); + } + // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. + // The generated template only serializes the typed options and drops these otherwise. + // ConnectOptions is request-only (never deserialized), so this map holds only what the + // caller set via the builder. Routed through QueryStringMapper to match the REST path. + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); @@ -487,11 +504,11 @@ private void handleIncomingMessage(String json) { return; } } - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException( - "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) - + "... Update your SDK version to support new message types.")); - } + // Unrecognized message type: forward-compatible no-op. The raw frame was + // already delivered to onMessage(String) above, so a newer server adding a + // benign control frame (e.g. a GA addition to this endpoint) must not surface + // as a fatal error to deployed clients. Routing it to onError would make a + // harmless frame look fatal; mirrors the speak v2 client and the JS/Python SDKs. } catch (Exception e) { if (onErrorHandler != null) { onErrorHandler.accept(e); diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak deleted file mode 100644 index 63f44e4..0000000 --- a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java.bak +++ /dev/null @@ -1,508 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.listen.v2.websocket; - -import com.deepgram.core.ClientOptions; -import com.deepgram.core.DisconnectReason; -import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; -import com.deepgram.core.ReconnectingWebSocketListener; -import com.deepgram.core.RequestOptions; -import com.deepgram.core.WebSocketReadyState; -import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; -import com.deepgram.resources.listen.v2.types.ListenV2Configure; -import com.deepgram.resources.listen.v2.types.ListenV2ConfigureFailure; -import com.deepgram.resources.listen.v2.types.ListenV2ConfigureSuccess; -import com.deepgram.resources.listen.v2.types.ListenV2Connected; -import com.deepgram.resources.listen.v2.types.ListenV2FatalError; -import com.deepgram.resources.listen.v2.types.ListenV2TurnInfo; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Consumer; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okio.ByteString; - -/** - * WebSocket client for the v2 channel. - * Provides real-time bidirectional communication with strongly-typed messages. - */ -public class V2WebSocketClient implements AutoCloseable { - protected final ClientOptions clientOptions; - - private final ObjectMapper objectMapper; - - private final OkHttpClient okHttpClient; - - private ScheduledExecutorService timeoutExecutor; - - private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; - - private volatile Runnable onConnectedHandler; - - private volatile Consumer onDisconnectedHandler; - - private volatile Consumer onErrorHandler; - - private volatile Consumer onMessageHandler; - - private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; - - private CompletableFuture connectionFuture; - - private ReconnectingWebSocketListener reconnectingListener; - - private volatile Consumer connectedHandler; - - private volatile Consumer turnInfoHandler; - - private volatile Consumer configureSuccessHandler; - - private volatile Consumer configureFailureHandler; - - private volatile Consumer errorHandler; - - /** - * Creates a new async WebSocket client for the v2 channel. - */ - public V2WebSocketClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.objectMapper = ObjectMappers.JSON_MAPPER; - this.okHttpClient = clientOptions.httpClient(); - } - - /** - * Establishes the WebSocket connection asynchronously with automatic reconnection. - * @return a CompletableFuture that completes when the connection is established - * @param options connection options including query parameters - */ - public CompletableFuture connect(V2ConnectOptions options) { - connectionFuture = new CompletableFuture<>(); - String baseUrl = clientOptions.environment().getProductionURL(); - String fullPath = "/v2/listen"; - if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { - fullPath = fullPath.substring(1); - } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { - fullPath = "/" + fullPath; - } - // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing - if (baseUrl.startsWith("wss://")) { - baseUrl = "https://" + baseUrl.substring(6); - } else if (baseUrl.startsWith("ws://")) { - baseUrl = "http://" + baseUrl.substring(5); - } - HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); - if (parsedUrl == null) { - throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); - } - HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); - urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); - if (options.getEncoding() != null && options.getEncoding().isPresent()) { - urlBuilder.addQueryParameter( - "encoding", String.valueOf(options.getEncoding().get())); - } - if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { - urlBuilder.addQueryParameter( - "sample_rate", String.valueOf(options.getSampleRate().get())); - } - if (options.getEagerEotThreshold() != null - && options.getEagerEotThreshold().isPresent()) { - urlBuilder.addQueryParameter( - "eager_eot_threshold", - String.valueOf(options.getEagerEotThreshold().get())); - } - if (options.getEotThreshold() != null && options.getEotThreshold().isPresent()) { - urlBuilder.addQueryParameter( - "eot_threshold", String.valueOf(options.getEotThreshold().get())); - } - if (options.getEotTimeoutMs() != null && options.getEotTimeoutMs().isPresent()) { - urlBuilder.addQueryParameter( - "eot_timeout_ms", String.valueOf(options.getEotTimeoutMs().get())); - } - if (options.getKeyterm() != null && options.getKeyterm().isPresent()) { - // Array-valued query params (String | List unions) must serialize as repeated - // params (keyterm=a&keyterm=b), not a stringified list. The generated streaming template - // uses String.valueOf(...), which mangles a List into "[a, b]"; route these through - // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. - QueryStringMapper.addQueryParameter( - urlBuilder, "keyterm", options.getKeyterm().get().get(), true); - } - if (options.getLanguageHint() != null && options.getLanguageHint().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "language_hint", options.getLanguageHint().get().get(), true); - } - if (options.getProfanityFilter() != null && options.getProfanityFilter().isPresent()) { - urlBuilder.addQueryParameter( - "profanity_filter", - String.valueOf(options.getProfanityFilter().get())); - } - if (options.getNumerals() != null && options.getNumerals().isPresent()) { - urlBuilder.addQueryParameter( - "numerals", String.valueOf(options.getNumerals().get())); - } - if (options.getRedact() != null && options.getRedact().isPresent()) { - urlBuilder.addQueryParameter( - "redact", String.valueOf(options.getRedact().get())); - } - if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { - urlBuilder.addQueryParameter( - "mip_opt_out", String.valueOf(options.getMipOptOut().get())); - } - if (options.getTag() != null && options.getTag().isPresent()) { - QueryStringMapper.addQueryParameter( - urlBuilder, "tag", options.getTag().get().get(), true); - } - // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. - // The generated template only serializes the typed options and drops these otherwise. - // ConnectOptions is request-only (never deserialized), so this map holds only what the - // caller set via the builder. Routed through QueryStringMapper to match the REST path. - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } - Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); - final Request request = requestBuilder.build(); - this.readyState = WebSocketReadyState.CONNECTING; - ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null - ? this.reconnectOptions - : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); - this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { - @Override - protected void onWebSocketOpen(WebSocket webSocket, Response response) { - readyState = WebSocketReadyState.OPEN; - if (onConnectedHandler != null) { - onConnectedHandler.run(); - } - connectionFuture.complete(null); - } - - @Override - protected void onWebSocketMessage(WebSocket webSocket, String text) { - handleIncomingMessage(text); - } - - @Override - protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) {} - - @Override - protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { - readyState = WebSocketReadyState.CLOSED; - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException(t)); - } - connectionFuture.completeExceptionally(t); - } - - @Override - protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { - readyState = WebSocketReadyState.CLOSED; - if (onDisconnectedHandler != null) { - onDisconnectedHandler.accept(new DisconnectReason(code, reason)); - } - } - }; - reconnectingListener.connect(); - return connectionFuture; - } - - /** - * Disconnects the WebSocket connection and releases resources. - */ - public void disconnect() { - reconnectingListener.disconnect(); - if (timeoutExecutor != null) { - timeoutExecutor.shutdownNow(); - timeoutExecutor = null; - } - } - - /** - * Gets the current state of the WebSocket connection. - * - * This provides the actual connection state, similar to the W3C WebSocket API. - * - * @return the current WebSocket ready state - */ - public WebSocketReadyState getReadyState() { - return readyState; - } - - /** - * Sends a ListenV2Media message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendMedia(ByteString message) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - // Use reconnecting listener's sendBinary method which handles queuing - reconnectingListener.sendBinary(message); - future.complete(null); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send binary data", e)); - } - return future; - } - - /** - * Sends a ListenV2CloseStream message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendCloseStream(ListenV2CloseStream message) { - return sendMessage(message); - } - - /** - * Sends a ListenV2Configure message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendConfigure(ListenV2Configure message) { - return sendMessage(message); - } - - /** - * Registers a handler for ListenV2Connected messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConnected(Consumer handler) { - this.connectedHandler = handler; - } - - /** - * Registers a handler for ListenV2TurnInfo messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onTurnInfo(Consumer handler) { - this.turnInfoHandler = handler; - } - - /** - * Registers a handler for ListenV2ConfigureSuccess messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConfigureSuccess(Consumer handler) { - this.configureSuccessHandler = handler; - } - - /** - * Registers a handler for ListenV2ConfigureFailure messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConfigureFailure(Consumer handler) { - this.configureFailureHandler = handler; - } - - /** - * Registers a handler for ListenV2FatalError messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onErrorMessage(Consumer handler) { - this.errorHandler = handler; - } - - /** - * Registers a handler called when the connection is established. - * @param handler the handler to invoke when connected - */ - public void onConnected(Runnable handler) { - this.onConnectedHandler = handler; - } - - /** - * Registers a handler called when the connection is closed. - * @param handler the handler to invoke when disconnected - */ - public void onDisconnected(Consumer handler) { - this.onDisconnectedHandler = handler; - } - - /** - * Registers a handler called when an error occurs. - * @param handler the handler to invoke on error - */ - public void onError(Consumer handler) { - this.onErrorHandler = handler; - } - - /** - * Registers a handler called for every incoming text message. - * The handler receives the raw JSON string before type-specific dispatch. - * @param handler the handler to invoke with the raw message JSON - */ - public void onMessage(Consumer handler) { - this.onMessageHandler = handler; - } - - /** - * Configures reconnection behavior. Must be called before {@link #connect}. - * - * @param options the reconnection options (backoff, retries, queue size) - */ - public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { - this.reconnectOptions = options; - } - - /** - * Closes this WebSocket client, releasing all resources. - * Equivalent to calling {@link #disconnect()}. - */ - @Override - public void close() { - disconnect(); - } - - /** - * Ensures the WebSocket is connected and ready to send messages. - * @throws IllegalStateException if the socket is not connected or not open - */ - private void assertSocketIsOpen() { - if (reconnectingListener.getWebSocket() == null) { - throw new IllegalStateException("WebSocket is not connected. Call connect() first."); - } - if (readyState != WebSocketReadyState.OPEN) { - throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); - } - } - - private CompletableFuture sendMessage(Object body) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - String json = objectMapper.writeValueAsString(body); - // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json); - future.complete(null); - } catch (IllegalStateException e) { - future.completeExceptionally(e); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send message", e)); - } - return future; - } - - private void handleIncomingMessage(String json) { - try { - if (onMessageHandler != null) { - onMessageHandler.accept(json); - } - JsonNode node = objectMapper.readTree(json); - if (node == null || node.isNull()) { - throw new IllegalArgumentException("Received null or invalid JSON message"); - } - if (node.has("request_id") - && node.has("sequence_id") - && node.has("event") - && node.has("turn_index") - && node.has("audio_window_start") - && node.has("audio_window_end") - && node.has("transcript") - && node.has("words") - && node.has("end_of_turn_confidence") - && "TurnInfo".equals(node.path("type").asText())) { - ListenV2TurnInfo turnInfoHandlerEvent = null; - try { - turnInfoHandlerEvent = objectMapper.treeToValue(node, ListenV2TurnInfo.class); - } catch (Exception e) { - } - if (turnInfoHandlerEvent != null) { - if (turnInfoHandler != null) { - turnInfoHandler.accept(turnInfoHandlerEvent); - } - return; - } - } - if (node.has("request_id") - && node.has("thresholds") - && node.has("keyterms") - && node.has("sequence_id") - && "ConfigureSuccess".equals(node.path("type").asText())) { - ListenV2ConfigureSuccess configureSuccessHandlerEvent = null; - try { - configureSuccessHandlerEvent = objectMapper.treeToValue(node, ListenV2ConfigureSuccess.class); - } catch (Exception e) { - } - if (configureSuccessHandlerEvent != null) { - if (configureSuccessHandler != null) { - configureSuccessHandler.accept(configureSuccessHandlerEvent); - } - return; - } - } - if (node.has("sequence_id") - && node.has("code") - && node.has("description") - && "Error".equals(node.path("type").asText())) { - ListenV2FatalError errorHandlerEvent = null; - try { - errorHandlerEvent = objectMapper.treeToValue(node, ListenV2FatalError.class); - } catch (Exception e) { - } - if (errorHandlerEvent != null) { - if (errorHandler != null) { - errorHandler.accept(errorHandlerEvent); - } - return; - } - } - if (node.has("request_id") - && node.has("sequence_id") - && "Connected".equals(node.path("type").asText())) { - ListenV2Connected connectedHandlerEvent = null; - try { - connectedHandlerEvent = objectMapper.treeToValue(node, ListenV2Connected.class); - } catch (Exception e) { - } - if (connectedHandlerEvent != null) { - if (connectedHandler != null) { - connectedHandler.accept(connectedHandlerEvent); - } - return; - } - } - if (node.has("request_id") - && node.has("sequence_id") - && "ConfigureFailure".equals(node.path("type").asText())) { - ListenV2ConfigureFailure configureFailureHandlerEvent = null; - try { - configureFailureHandlerEvent = objectMapper.treeToValue(node, ListenV2ConfigureFailure.class); - } catch (Exception e) { - } - if (configureFailureHandlerEvent != null) { - if (configureFailureHandler != null) { - configureFailureHandler.accept(configureFailureHandlerEvent); - } - return; - } - } - // Unrecognized message type: forward-compatible no-op. The raw frame was - // already delivered to onMessage(String) above, so a newer server adding a - // benign control frame (e.g. a GA addition to this endpoint) must not surface - // as a fatal error to deployed clients. Routing it to onError would make a - // harmless frame look fatal; mirrors the speak v2 client and the JS/Python SDKs. - } catch (Exception e) { - if (onErrorHandler != null) { - onErrorHandler.accept(e); - } - } - } -} diff --git a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java index 8af4275..8033a55 100644 --- a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java @@ -6,6 +6,7 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -122,6 +123,17 @@ public CompletableFuture connect(V1ConnectOptions options) { urlBuilder.addQueryParameter( "speed", String.valueOf(options.getSpeed().get())); } + // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. + // The generated template only serializes the typed options and drops these otherwise. + // ConnectOptions is request-only (never deserialized), so this map holds only what the + // caller set via the builder. Routed through QueryStringMapper to match the REST path. + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); + } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); final Request request = requestBuilder.build(); diff --git a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak deleted file mode 100644 index 8033a55..0000000 --- a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java.bak +++ /dev/null @@ -1,458 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.speak.v1.websocket; - -import com.deepgram.core.ClientOptions; -import com.deepgram.core.DisconnectReason; -import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; -import com.deepgram.core.ReconnectingWebSocketListener; -import com.deepgram.core.RequestOptions; -import com.deepgram.core.WebSocketReadyState; -import com.deepgram.resources.speak.v1.types.SpeakV1Clear; -import com.deepgram.resources.speak.v1.types.SpeakV1Cleared; -import com.deepgram.resources.speak.v1.types.SpeakV1Close; -import com.deepgram.resources.speak.v1.types.SpeakV1Flush; -import com.deepgram.resources.speak.v1.types.SpeakV1Flushed; -import com.deepgram.resources.speak.v1.types.SpeakV1Metadata; -import com.deepgram.resources.speak.v1.types.SpeakV1Text; -import com.deepgram.resources.speak.v1.types.SpeakV1Warning; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Consumer; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okio.ByteString; - -/** - * WebSocket client for the v1 channel. - * Provides real-time bidirectional communication with strongly-typed messages. - */ -public class V1WebSocketClient implements AutoCloseable { - protected final ClientOptions clientOptions; - - private final ObjectMapper objectMapper; - - private final OkHttpClient okHttpClient; - - private ScheduledExecutorService timeoutExecutor; - - private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; - - private volatile Runnable onConnectedHandler; - - private volatile Consumer onDisconnectedHandler; - - private volatile Consumer onErrorHandler; - - private volatile Consumer onMessageHandler; - - private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; - - private CompletableFuture connectionFuture; - - private ReconnectingWebSocketListener reconnectingListener; - - private volatile Consumer speakV1AudioHandler; - - private volatile Consumer metadataHandler; - - private volatile Consumer flushedHandler; - - private volatile Consumer clearedHandler; - - private volatile Consumer warningHandler; - - /** - * Creates a new async WebSocket client for the v1 channel. - */ - public V1WebSocketClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.objectMapper = ObjectMappers.JSON_MAPPER; - this.okHttpClient = clientOptions.httpClient(); - } - - /** - * Establishes the WebSocket connection asynchronously with automatic reconnection. - * @return a CompletableFuture that completes when the connection is established - * @param options connection options including query parameters - */ - public CompletableFuture connect(V1ConnectOptions options) { - connectionFuture = new CompletableFuture<>(); - String baseUrl = clientOptions.environment().getProductionURL(); - String fullPath = "/v1/speak"; - if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { - fullPath = fullPath.substring(1); - } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { - fullPath = "/" + fullPath; - } - // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing - if (baseUrl.startsWith("wss://")) { - baseUrl = "https://" + baseUrl.substring(6); - } else if (baseUrl.startsWith("ws://")) { - baseUrl = "http://" + baseUrl.substring(5); - } - HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); - if (parsedUrl == null) { - throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); - } - HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); - if (options.getEncoding() != null && options.getEncoding().isPresent()) { - urlBuilder.addQueryParameter( - "encoding", String.valueOf(options.getEncoding().get())); - } - if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { - urlBuilder.addQueryParameter( - "mip_opt_out", String.valueOf(options.getMipOptOut().get())); - } - if (options.getModel() != null && options.getModel().isPresent()) { - urlBuilder.addQueryParameter( - "model", String.valueOf(options.getModel().get())); - } - if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { - urlBuilder.addQueryParameter( - "sample_rate", String.valueOf(options.getSampleRate().get())); - } - if (options.getSpeed() != null && options.getSpeed().isPresent()) { - urlBuilder.addQueryParameter( - "speed", String.valueOf(options.getSpeed().get())); - } - // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. - // The generated template only serializes the typed options and drops these otherwise. - // ConnectOptions is request-only (never deserialized), so this map holds only what the - // caller set via the builder. Routed through QueryStringMapper to match the REST path. - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } - Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); - final Request request = requestBuilder.build(); - this.readyState = WebSocketReadyState.CONNECTING; - ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null - ? this.reconnectOptions - : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); - this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { - @Override - protected void onWebSocketOpen(WebSocket webSocket, Response response) { - readyState = WebSocketReadyState.OPEN; - if (onConnectedHandler != null) { - onConnectedHandler.run(); - } - connectionFuture.complete(null); - } - - @Override - protected void onWebSocketMessage(WebSocket webSocket, String text) { - handleIncomingMessage(text); - } - - @Override - protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { - if (speakV1AudioHandler != null) { - speakV1AudioHandler.accept(bytes); - } - } - - @Override - protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { - readyState = WebSocketReadyState.CLOSED; - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException(t)); - } - connectionFuture.completeExceptionally(t); - } - - @Override - protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { - readyState = WebSocketReadyState.CLOSED; - if (onDisconnectedHandler != null) { - onDisconnectedHandler.accept(new DisconnectReason(code, reason)); - } - } - }; - reconnectingListener.connect(); - return connectionFuture; - } - - /** - * Establishes the WebSocket connection asynchronously with default options. - * @return a CompletableFuture that completes when the connection is established - */ - public CompletableFuture connect() { - return connect(V1ConnectOptions.builder().build()); - } - - /** - * Disconnects the WebSocket connection and releases resources. - */ - public void disconnect() { - reconnectingListener.disconnect(); - if (timeoutExecutor != null) { - timeoutExecutor.shutdownNow(); - timeoutExecutor = null; - } - } - - /** - * Gets the current state of the WebSocket connection. - * - * This provides the actual connection state, similar to the W3C WebSocket API. - * - * @return the current WebSocket ready state - */ - public WebSocketReadyState getReadyState() { - return readyState; - } - - /** - * Sends a SpeakV1Text message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendText(SpeakV1Text message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV1Flush message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendFlush(SpeakV1Flush message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV1Clear message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendClear(SpeakV1Clear message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV1Close message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendClose(SpeakV1Close message) { - return sendMessage(message); - } - - /** - * Registers a handler for SpeakV1Audio messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeakV1Audio(Consumer handler) { - this.speakV1AudioHandler = handler; - } - - /** - * Registers a handler for SpeakV1Metadata messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onMetadata(Consumer handler) { - this.metadataHandler = handler; - } - - /** - * Registers a handler for SpeakV1Flushed messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onFlushed(Consumer handler) { - this.flushedHandler = handler; - } - - /** - * Registers a handler for SpeakV1Cleared messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onCleared(Consumer handler) { - this.clearedHandler = handler; - } - - /** - * Registers a handler for SpeakV1Warning messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onWarning(Consumer handler) { - this.warningHandler = handler; - } - - /** - * Registers a handler called when the connection is established. - * @param handler the handler to invoke when connected - */ - public void onConnected(Runnable handler) { - this.onConnectedHandler = handler; - } - - /** - * Registers a handler called when the connection is closed. - * @param handler the handler to invoke when disconnected - */ - public void onDisconnected(Consumer handler) { - this.onDisconnectedHandler = handler; - } - - /** - * Registers a handler called when an error occurs. - * @param handler the handler to invoke on error - */ - public void onError(Consumer handler) { - this.onErrorHandler = handler; - } - - /** - * Registers a handler called for every incoming text message. - * The handler receives the raw JSON string before type-specific dispatch. - * @param handler the handler to invoke with the raw message JSON - */ - public void onMessage(Consumer handler) { - this.onMessageHandler = handler; - } - - /** - * Configures reconnection behavior. Must be called before {@link #connect}. - * - * @param options the reconnection options (backoff, retries, queue size) - */ - public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { - this.reconnectOptions = options; - } - - /** - * Closes this WebSocket client, releasing all resources. - * Equivalent to calling {@link #disconnect()}. - */ - @Override - public void close() { - disconnect(); - } - - /** - * Ensures the WebSocket is connected and ready to send messages. - * @throws IllegalStateException if the socket is not connected or not open - */ - private void assertSocketIsOpen() { - if (reconnectingListener.getWebSocket() == null) { - throw new IllegalStateException("WebSocket is not connected. Call connect() first."); - } - if (readyState != WebSocketReadyState.OPEN) { - throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); - } - } - - private CompletableFuture sendMessage(Object body) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - String json = objectMapper.writeValueAsString(body); - // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json); - future.complete(null); - } catch (IllegalStateException e) { - future.completeExceptionally(e); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send message", e)); - } - return future; - } - - private void handleIncomingMessage(String json) { - try { - if (onMessageHandler != null) { - onMessageHandler.accept(json); - } - JsonNode node = objectMapper.readTree(json); - if (node == null || node.isNull()) { - throw new IllegalArgumentException("Received null or invalid JSON message"); - } - if (node.has("request_id") - && node.has("model_name") - && node.has("model_version") - && node.has("model_uuid") - && "Metadata".equals(node.path("type").asText())) { - SpeakV1Metadata metadataHandlerEvent = null; - try { - metadataHandlerEvent = objectMapper.treeToValue(node, SpeakV1Metadata.class); - } catch (Exception e) { - } - if (metadataHandlerEvent != null) { - if (metadataHandler != null) { - metadataHandler.accept(metadataHandlerEvent); - } - return; - } - } - if (node.has("description") - && node.has("code") - && "Warning".equals(node.path("type").asText())) { - SpeakV1Warning warningHandlerEvent = null; - try { - warningHandlerEvent = objectMapper.treeToValue(node, SpeakV1Warning.class); - } catch (Exception e) { - } - if (warningHandlerEvent != null) { - if (warningHandler != null) { - warningHandler.accept(warningHandlerEvent); - } - return; - } - } - if (node.has("type") && node.has("sequence_id")) { - SpeakV1Flushed flushedHandlerEvent = null; - try { - flushedHandlerEvent = objectMapper.treeToValue(node, SpeakV1Flushed.class); - } catch (Exception e) { - } - if (flushedHandlerEvent != null) { - if (flushedHandler != null) { - flushedHandler.accept(flushedHandlerEvent); - } - return; - } - } - if (node.has("type") && node.has("sequence_id")) { - SpeakV1Cleared clearedHandlerEvent = null; - try { - clearedHandlerEvent = objectMapper.treeToValue(node, SpeakV1Cleared.class); - } catch (Exception e) { - } - if (clearedHandlerEvent != null) { - if (clearedHandler != null) { - clearedHandler.accept(clearedHandlerEvent); - } - return; - } - } - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException( - "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) - + "... Update your SDK version to support new message types.")); - } - } catch (Exception e) { - if (onErrorHandler != null) { - onErrorHandler.accept(e); - } - } - } -} diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java index 2e96a5c..51fef19 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof SpeakV2Close; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak deleted file mode 100644 index 51fef19..0000000 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java.bak +++ /dev/null @@ -1,86 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.speak.v2.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SpeakV2Close.Builder.class) -public final class SpeakV2Close { - private final Map additionalProperties; - - private SpeakV2Close(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier - */ - @JsonProperty("type") - public String getType() { - return "Close"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SpeakV2Close; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SpeakV2Close other) { - return this; - } - - public SpeakV2Close build() { - return new SpeakV2Close(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java index acd717d..e122400 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java +++ b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof SpeakV2Flush; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak b/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak deleted file mode 100644 index e122400..0000000 --- a/src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java.bak +++ /dev/null @@ -1,86 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.speak.v2.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SpeakV2Flush.Builder.class) -public final class SpeakV2Flush { - private final Map additionalProperties; - - private SpeakV2Flush(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - - /** - * @return Message type identifier - */ - @JsonProperty("type") - public String getType() { - return "Flush"; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SpeakV2Flush; - } - - @java.lang.Override - public int hashCode() { - // Manual patch: Fern generates equals() (all instances of this fields-less message - // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a - // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. - return getClass().hashCode(); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SpeakV2Flush other) { - return this; - } - - public SpeakV2Flush build() { - return new SpeakV2Flush(additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java index 3e032b1..2a8dc12 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java @@ -6,6 +6,7 @@ import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; +import com.deepgram.core.QueryStringMapper; import com.deepgram.core.ReconnectingWebSocketListener; import com.deepgram.core.RequestOptions; import com.deepgram.core.WebSocketReadyState; @@ -143,7 +144,23 @@ public CompletableFuture connect(V2ConnectOptions options) { "mip_opt_out", String.valueOf(options.getMipOptOut().get())); } if (options.getTag() != null && options.getTag().isPresent()) { - urlBuilder.addQueryParameter("tag", String.valueOf(options.getTag().get())); + // Array-valued query params (String | List unions) must serialize as repeated + // params (tag=a&tag=b), not a stringified list. The generated streaming template uses + // String.valueOf(...), which mangles a List into "[a, b]"; route through + // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. + QueryStringMapper.addQueryParameter( + urlBuilder, "tag", options.getTag().get().get(), true); + } + // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. + // The generated template only serializes the typed options and drops these otherwise. + // ConnectOptions is request-only (never deserialized), so this map holds only what the + // caller set via the builder. Routed through QueryStringMapper to match the REST path. + if (options.getAdditionalProperties() != null) { + options.getAdditionalProperties().forEach((key, value) -> { + if (value != null) { + QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); + } + }); } Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); @@ -598,11 +615,11 @@ private void handleIncomingMessage(String json) { return; } } - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException( - "Unrecognized WebSocket message: " + json.substring(0, Math.min(200, json.length())) - + "... Update your SDK version to support new message types.")); - } + // Unrecognized message type: forward-compatible no-op. The raw frame was + // already delivered to onMessage(String) above, so a newer server adding a + // benign control frame (e.g. a GA addition to this endpoint) must not surface + // as a fatal error to deployed clients. Routing it to onError would make a + // harmless frame look fatal to a voice agent; mirrors the JS/Python SDKs. } catch (Exception e) { if (onErrorHandler != null) { onErrorHandler.accept(e); diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak deleted file mode 100644 index 2a8dc12..0000000 --- a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java.bak +++ /dev/null @@ -1,629 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.resources.speak.v2.websocket; - -import com.deepgram.core.ClientOptions; -import com.deepgram.core.DisconnectReason; -import com.deepgram.core.ObjectMappers; -import com.deepgram.core.QueryStringMapper; -import com.deepgram.core.ReconnectingWebSocketListener; -import com.deepgram.core.RequestOptions; -import com.deepgram.core.WebSocketReadyState; -import com.deepgram.resources.speak.v2.types.SpeakV2Close; -import com.deepgram.resources.speak.v2.types.SpeakV2Configure; -import com.deepgram.resources.speak.v2.types.SpeakV2ConfigureFailure; -import com.deepgram.resources.speak.v2.types.SpeakV2ConfigureSuccess; -import com.deepgram.resources.speak.v2.types.SpeakV2Connected; -import com.deepgram.resources.speak.v2.types.SpeakV2Error; -import com.deepgram.resources.speak.v2.types.SpeakV2Flush; -import com.deepgram.resources.speak.v2.types.SpeakV2Flushed; -import com.deepgram.resources.speak.v2.types.SpeakV2Interrupt; -import com.deepgram.resources.speak.v2.types.SpeakV2SessionMetadata; -import com.deepgram.resources.speak.v2.types.SpeakV2Speak; -import com.deepgram.resources.speak.v2.types.SpeakV2SpeechInterrupted; -import com.deepgram.resources.speak.v2.types.SpeakV2SpeechMetadata; -import com.deepgram.resources.speak.v2.types.SpeakV2SpeechStarted; -import com.deepgram.resources.speak.v2.types.SpeakV2Warning; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Consumer; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okio.ByteString; - -/** - * WebSocket client for the v2 channel. - * Provides real-time bidirectional communication with strongly-typed messages. - */ -public class V2WebSocketClient implements AutoCloseable { - protected final ClientOptions clientOptions; - - private final ObjectMapper objectMapper; - - private final OkHttpClient okHttpClient; - - private ScheduledExecutorService timeoutExecutor; - - private volatile WebSocketReadyState readyState = WebSocketReadyState.CLOSED; - - private volatile Runnable onConnectedHandler; - - private volatile Consumer onDisconnectedHandler; - - private volatile Consumer onErrorHandler; - - private volatile Consumer onMessageHandler; - - private volatile ReconnectingWebSocketListener.ReconnectOptions reconnectOptions; - - private CompletableFuture connectionFuture; - - private ReconnectingWebSocketListener reconnectingListener; - - private volatile Consumer speakV2AudioHandler; - - private volatile Consumer connectedHandler; - - private volatile Consumer speechStartedHandler; - - private volatile Consumer speechMetadataHandler; - - private volatile Consumer speechInterruptedHandler; - - private volatile Consumer flushedHandler; - - private volatile Consumer sessionMetadataHandler; - - private volatile Consumer configureSuccessHandler; - - private volatile Consumer configureFailureHandler; - - private volatile Consumer warningHandler; - - private volatile Consumer errorHandler; - - /** - * Creates a new async WebSocket client for the v2 channel. - */ - public V2WebSocketClient(ClientOptions clientOptions) { - this.clientOptions = clientOptions; - this.objectMapper = ObjectMappers.JSON_MAPPER; - this.okHttpClient = clientOptions.httpClient(); - } - - /** - * Establishes the WebSocket connection asynchronously with automatic reconnection. - * @return a CompletableFuture that completes when the connection is established - * @param options connection options including query parameters - */ - public CompletableFuture connect(V2ConnectOptions options) { - connectionFuture = new CompletableFuture<>(); - String baseUrl = clientOptions.environment().getProductionURL(); - String fullPath = "/v2/speak"; - if (baseUrl.endsWith("/") && fullPath.startsWith("/")) { - fullPath = fullPath.substring(1); - } else if (!baseUrl.endsWith("/") && !fullPath.startsWith("/")) { - fullPath = "/" + fullPath; - } - // OkHttp's HttpUrl only supports http/https schemes; convert wss/ws for URL parsing - if (baseUrl.startsWith("wss://")) { - baseUrl = "https://" + baseUrl.substring(6); - } else if (baseUrl.startsWith("ws://")) { - baseUrl = "http://" + baseUrl.substring(5); - } - HttpUrl parsedUrl = HttpUrl.parse(baseUrl + fullPath); - if (parsedUrl == null) { - throw new IllegalArgumentException("Invalid WebSocket URL: " + baseUrl + fullPath); - } - HttpUrl.Builder urlBuilder = parsedUrl.newBuilder(); - urlBuilder.addQueryParameter("model", String.valueOf(options.getModel())); - if (options.getEncoding() != null && options.getEncoding().isPresent()) { - urlBuilder.addQueryParameter( - "encoding", String.valueOf(options.getEncoding().get())); - } - if (options.getSampleRate() != null && options.getSampleRate().isPresent()) { - urlBuilder.addQueryParameter( - "sample_rate", String.valueOf(options.getSampleRate().get())); - } - if (options.getSpeed() != null && options.getSpeed().isPresent()) { - urlBuilder.addQueryParameter( - "speed", String.valueOf(options.getSpeed().get())); - } - if (options.getExpressivity() != null && options.getExpressivity().isPresent()) { - urlBuilder.addQueryParameter( - "expressivity", String.valueOf(options.getExpressivity().get())); - } - if (options.getMipOptOut() != null && options.getMipOptOut().isPresent()) { - urlBuilder.addQueryParameter( - "mip_opt_out", String.valueOf(options.getMipOptOut().get())); - } - if (options.getTag() != null && options.getTag().isPresent()) { - // Array-valued query params (String | List unions) must serialize as repeated - // params (tag=a&tag=b), not a stringified list. The generated streaming template uses - // String.valueOf(...), which mangles a List into "[a, b]"; route through - // QueryStringMapper (arraysAsRepeats=true) so the wire format matches the REST path. - QueryStringMapper.addQueryParameter( - urlBuilder, "tag", options.getTag().get().get(), true); - } - // Escape hatch: emit caller-supplied additionalProperties (e.g. no_delay) as query params. - // The generated template only serializes the typed options and drops these otherwise. - // ConnectOptions is request-only (never deserialized), so this map holds only what the - // caller set via the builder. Routed through QueryStringMapper to match the REST path. - if (options.getAdditionalProperties() != null) { - options.getAdditionalProperties().forEach((key, value) -> { - if (value != null) { - QueryStringMapper.addQueryParameter(urlBuilder, key, value, true); - } - }); - } - Request.Builder requestBuilder = new Request.Builder().url(urlBuilder.build()); - clientOptions.headers((RequestOptions) null).forEach(requestBuilder::addHeader); - final Request request = requestBuilder.build(); - this.readyState = WebSocketReadyState.CONNECTING; - ReconnectingWebSocketListener.ReconnectOptions reconnectOpts = this.reconnectOptions != null - ? this.reconnectOptions - : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); - this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { - @Override - protected void onWebSocketOpen(WebSocket webSocket, Response response) { - readyState = WebSocketReadyState.OPEN; - if (onConnectedHandler != null) { - onConnectedHandler.run(); - } - connectionFuture.complete(null); - } - - @Override - protected void onWebSocketMessage(WebSocket webSocket, String text) { - handleIncomingMessage(text); - } - - @Override - protected void onWebSocketBinaryMessage(WebSocket webSocket, ByteString bytes) { - if (speakV2AudioHandler != null) { - speakV2AudioHandler.accept(bytes); - } - } - - @Override - protected void onWebSocketFailure(WebSocket webSocket, Throwable t, Response response) { - readyState = WebSocketReadyState.CLOSED; - if (onErrorHandler != null) { - onErrorHandler.accept(new RuntimeException(t)); - } - connectionFuture.completeExceptionally(t); - } - - @Override - protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { - readyState = WebSocketReadyState.CLOSED; - if (onDisconnectedHandler != null) { - onDisconnectedHandler.accept(new DisconnectReason(code, reason)); - } - } - }; - reconnectingListener.connect(); - return connectionFuture; - } - - /** - * Disconnects the WebSocket connection and releases resources. - */ - public void disconnect() { - reconnectingListener.disconnect(); - if (timeoutExecutor != null) { - timeoutExecutor.shutdownNow(); - timeoutExecutor = null; - } - } - - /** - * Gets the current state of the WebSocket connection. - * - * This provides the actual connection state, similar to the W3C WebSocket API. - * - * @return the current WebSocket ready state - */ - public WebSocketReadyState getReadyState() { - return readyState; - } - - /** - * Sends a SpeakV2Speak message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendSpeak(SpeakV2Speak message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV2Flush message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendFlush(SpeakV2Flush message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV2Interrupt message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendInterrupt(SpeakV2Interrupt message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV2Configure message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendConfigure(SpeakV2Configure message) { - return sendMessage(message); - } - - /** - * Sends a SpeakV2Close message to the server asynchronously. - * @param message the message to send - * @return a CompletableFuture that completes when the message is sent - */ - public CompletableFuture sendClose(SpeakV2Close message) { - return sendMessage(message); - } - - /** - * Registers a handler for SpeakV2Audio messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeakV2Audio(Consumer handler) { - this.speakV2AudioHandler = handler; - } - - /** - * Registers a handler for SpeakV2Connected messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConnected(Consumer handler) { - this.connectedHandler = handler; - } - - /** - * Registers a handler for SpeakV2SpeechStarted messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeechStarted(Consumer handler) { - this.speechStartedHandler = handler; - } - - /** - * Registers a handler for SpeakV2SpeechMetadata messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeechMetadata(Consumer handler) { - this.speechMetadataHandler = handler; - } - - /** - * Registers a handler for SpeakV2SpeechInterrupted messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSpeechInterrupted(Consumer handler) { - this.speechInterruptedHandler = handler; - } - - /** - * Registers a handler for SpeakV2Flushed messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onFlushed(Consumer handler) { - this.flushedHandler = handler; - } - - /** - * Registers a handler for SpeakV2SessionMetadata messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onSessionMetadata(Consumer handler) { - this.sessionMetadataHandler = handler; - } - - /** - * Registers a handler for SpeakV2ConfigureSuccess messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConfigureSuccess(Consumer handler) { - this.configureSuccessHandler = handler; - } - - /** - * Registers a handler for SpeakV2ConfigureFailure messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onConfigureFailure(Consumer handler) { - this.configureFailureHandler = handler; - } - - /** - * Registers a handler for SpeakV2Warning messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onWarning(Consumer handler) { - this.warningHandler = handler; - } - - /** - * Registers a handler for SpeakV2Error messages from the server. - * @param handler the handler to invoke when a message is received - */ - public void onErrorMessage(Consumer handler) { - this.errorHandler = handler; - } - - /** - * Registers a handler called when the connection is established. - * @param handler the handler to invoke when connected - */ - public void onConnected(Runnable handler) { - this.onConnectedHandler = handler; - } - - /** - * Registers a handler called when the connection is closed. - * @param handler the handler to invoke when disconnected - */ - public void onDisconnected(Consumer handler) { - this.onDisconnectedHandler = handler; - } - - /** - * Registers a handler called when an error occurs. - * @param handler the handler to invoke on error - */ - public void onError(Consumer handler) { - this.onErrorHandler = handler; - } - - /** - * Registers a handler called for every incoming text message. - * The handler receives the raw JSON string before type-specific dispatch. - * @param handler the handler to invoke with the raw message JSON - */ - public void onMessage(Consumer handler) { - this.onMessageHandler = handler; - } - - /** - * Configures reconnection behavior. Must be called before {@link #connect}. - * - * @param options the reconnection options (backoff, retries, queue size) - */ - public void reconnectOptions(ReconnectingWebSocketListener.ReconnectOptions options) { - this.reconnectOptions = options; - } - - /** - * Closes this WebSocket client, releasing all resources. - * Equivalent to calling {@link #disconnect()}. - */ - @Override - public void close() { - disconnect(); - } - - /** - * Ensures the WebSocket is connected and ready to send messages. - * @throws IllegalStateException if the socket is not connected or not open - */ - private void assertSocketIsOpen() { - if (reconnectingListener.getWebSocket() == null) { - throw new IllegalStateException("WebSocket is not connected. Call connect() first."); - } - if (readyState != WebSocketReadyState.OPEN) { - throw new IllegalStateException("WebSocket is not open. Current state: " + readyState); - } - } - - private CompletableFuture sendMessage(Object body) { - CompletableFuture future = new CompletableFuture<>(); - try { - assertSocketIsOpen(); - String json = objectMapper.writeValueAsString(body); - // Use reconnecting listener's send method which handles queuing - reconnectingListener.send(json); - future.complete(null); - } catch (IllegalStateException e) { - future.completeExceptionally(e); - } catch (Exception e) { - future.completeExceptionally(new RuntimeException("Failed to send message", e)); - } - return future; - } - - private void handleIncomingMessage(String json) { - try { - if (onMessageHandler != null) { - onMessageHandler.accept(json); - } - JsonNode node = objectMapper.readTree(json); - if (node == null || node.isNull()) { - throw new IllegalArgumentException("Received null or invalid JSON message"); - } - if (node.has("speech_id") - && node.has("audio_duration_ms") - && node.has("input_character_count") - && node.has("billable_character_count") - && node.has("controls_applied") - && "SpeechMetadata".equals(node.path("type").asText())) { - SpeakV2SpeechMetadata speechMetadataHandlerEvent = null; - try { - speechMetadataHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechMetadata.class); - } catch (Exception e) { - } - if (speechMetadataHandlerEvent != null) { - if (speechMetadataHandler != null) { - speechMetadataHandler.accept(speechMetadataHandlerEvent); - } - return; - } - } - if (node.has("request_id") - && node.has("model_name") - && node.has("model_version") - && node.has("model_uuids") - && "Connected".equals(node.path("type").asText())) { - SpeakV2Connected connectedHandlerEvent = null; - try { - connectedHandlerEvent = objectMapper.treeToValue(node, SpeakV2Connected.class); - } catch (Exception e) { - } - if (connectedHandlerEvent != null) { - if (connectedHandler != null) { - connectedHandler.accept(connectedHandlerEvent); - } - return; - } - } - if (node.has("total_audio_duration_ms") - && node.has("total_input_character_count") - && node.has("total_billable_character_count") - && "SessionMetadata".equals(node.path("type").asText())) { - SpeakV2SessionMetadata sessionMetadataHandlerEvent = null; - try { - sessionMetadataHandlerEvent = objectMapper.treeToValue(node, SpeakV2SessionMetadata.class); - } catch (Exception e) { - } - if (sessionMetadataHandlerEvent != null) { - if (sessionMetadataHandler != null) { - sessionMetadataHandler.accept(sessionMetadataHandlerEvent); - } - return; - } - } - if (node.has("audio_played_ms") - && node.has("metadata") - && "SpeechInterrupted".equals(node.path("type").asText())) { - SpeakV2SpeechInterrupted speechInterruptedHandlerEvent = null; - try { - speechInterruptedHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechInterrupted.class); - } catch (Exception e) { - } - if (speechInterruptedHandlerEvent != null) { - if (speechInterruptedHandler != null) { - speechInterruptedHandler.accept(speechInterruptedHandlerEvent); - } - return; - } - } - if (node.has("code") - && node.has("description") - && "ConfigureFailure".equals(node.path("type").asText())) { - SpeakV2ConfigureFailure configureFailureHandlerEvent = null; - try { - configureFailureHandlerEvent = objectMapper.treeToValue(node, SpeakV2ConfigureFailure.class); - } catch (Exception e) { - } - if (configureFailureHandlerEvent != null) { - if (configureFailureHandler != null) { - configureFailureHandler.accept(configureFailureHandlerEvent); - } - return; - } - } - if (node.has("code") - && node.has("description") - && "Warning".equals(node.path("type").asText())) { - SpeakV2Warning warningHandlerEvent = null; - try { - warningHandlerEvent = objectMapper.treeToValue(node, SpeakV2Warning.class); - } catch (Exception e) { - } - if (warningHandlerEvent != null) { - if (warningHandler != null) { - warningHandler.accept(warningHandlerEvent); - } - return; - } - } - if (node.has("code") - && node.has("description") - && "Error".equals(node.path("type").asText())) { - SpeakV2Error errorHandlerEvent = null; - try { - errorHandlerEvent = objectMapper.treeToValue(node, SpeakV2Error.class); - } catch (Exception e) { - } - if (errorHandlerEvent != null) { - if (errorHandler != null) { - errorHandler.accept(errorHandlerEvent); - } - return; - } - } - if (node.has("speech_id") - && "SpeechStarted".equals(node.path("type").asText())) { - SpeakV2SpeechStarted speechStartedHandlerEvent = null; - try { - speechStartedHandlerEvent = objectMapper.treeToValue(node, SpeakV2SpeechStarted.class); - } catch (Exception e) { - } - if (speechStartedHandlerEvent != null) { - if (speechStartedHandler != null) { - speechStartedHandler.accept(speechStartedHandlerEvent); - } - return; - } - } - if (node.has("speech_id") && "Flushed".equals(node.path("type").asText())) { - SpeakV2Flushed flushedHandlerEvent = null; - try { - flushedHandlerEvent = objectMapper.treeToValue(node, SpeakV2Flushed.class); - } catch (Exception e) { - } - if (flushedHandlerEvent != null) { - if (flushedHandler != null) { - flushedHandler.accept(flushedHandlerEvent); - } - return; - } - } - if (node.has("applied") - && "ConfigureSuccess".equals(node.path("type").asText())) { - SpeakV2ConfigureSuccess configureSuccessHandlerEvent = null; - try { - configureSuccessHandlerEvent = objectMapper.treeToValue(node, SpeakV2ConfigureSuccess.class); - } catch (Exception e) { - } - if (configureSuccessHandlerEvent != null) { - if (configureSuccessHandler != null) { - configureSuccessHandler.accept(configureSuccessHandlerEvent); - } - return; - } - } - // Unrecognized message type: forward-compatible no-op. The raw frame was - // already delivered to onMessage(String) above, so a newer server adding a - // benign control frame (e.g. a GA addition to this endpoint) must not surface - // as a fatal error to deployed clients. Routing it to onError would make a - // harmless frame look fatal to a voice agent; mirrors the JS/Python SDKs. - } catch (Exception e) { - if (onErrorHandler != null) { - onErrorHandler.accept(e); - } - } - } -} diff --git a/src/test/java/com/deepgram/RegenTypesTest.java b/src/test/java/com/deepgram/RegenTypesTest.java index 8b5c328..e6b6165 100644 --- a/src/test/java/com/deepgram/RegenTypesTest.java +++ b/src/test/java/com/deepgram/RegenTypesTest.java @@ -18,8 +18,8 @@ import com.deepgram.resources.speak.v2.types.SpeakV2Flush; import com.deepgram.types.DeepgramListenProviderV2; import com.deepgram.types.Google; -import com.deepgram.types.GoogleThinkProviderModel; -import com.deepgram.types.GoogleThinkProviderVersion; +import com.deepgram.types.GoogleModel; +import com.deepgram.types.GoogleVersion; import com.deepgram.types.ListenV2Redact; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; @@ -179,14 +179,14 @@ void updateListenProviderUnionV2() throws Exception { } @Test - @DisplayName("Google.version is a GoogleThinkProviderVersion enum serializing to its wire value") + @DisplayName("Google.version is a GoogleVersion enum serializing to its wire value") void googleVersionEnum() throws Exception { Google google = Google.builder() - .model(GoogleThinkProviderModel.GEMINI25FLASH) - .version(GoogleThinkProviderVersion.V1BETA) + .model(GoogleModel.GEMINI25FLASH) + .version(GoogleVersion.V1BETA) .build(); - assertThat(google.getVersion()).contains(GoogleThinkProviderVersion.V1BETA); + assertThat(google.getVersion()).contains(GoogleVersion.V1BETA); assertThat(MAPPER.writeValueAsString(google)).contains("\"version\":\"v1beta\""); } diff --git a/src/test/java/com/deepgram/SpeakV2ConnectWireTest.java b/src/test/java/com/deepgram/SpeakV2ConnectWireTest.java index 5d899ef..37e57db 100644 --- a/src/test/java/com/deepgram/SpeakV2ConnectWireTest.java +++ b/src/test/java/com/deepgram/SpeakV2ConnectWireTest.java @@ -8,6 +8,7 @@ import com.deepgram.resources.speak.v2.types.SpeakV2InterruptPlaybackOffset; import com.deepgram.resources.speak.v2.websocket.V2ConnectOptions; import com.deepgram.resources.speak.v2.websocket.V2WebSocketClient; +import com.deepgram.types.SpeakV2Speed; import com.deepgram.types.SpeakV2Tag; import java.util.List; import java.util.concurrent.BlockingQueue; @@ -98,7 +99,7 @@ void tagStringSentAsOneParam() throws Exception { void speedPresentWhenSet() throws Exception { HttpUrl url = connectAndCaptureUrl(V2ConnectOptions.builder() .model("flux-alexis-en") - .speed(1.05) + .speed(SpeakV2Speed.ONE_POINT_ZERO_FIVE) .build()); assertThat(url.queryParameter("speed")).isEqualTo("1.05"); From baae2942751e88743077046d191a108424f99fdc Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 10:48:10 +0100 Subject: [PATCH 05/12] fix: patch ListenV2ForceEndTurn hashCode, correct ClientOptions rationale Two gaps in the preceding regen-reconciliation commit. 1. ListenV2ForceEndTurn was generated unpatched. It is a 12th fields-less message type with equals() and no hashCode(), so it violated the Object contract like the other 11. Diffing .bak files could not catch it: the type is newly generated (reverted out in #91, reintroduced by 4.18.0), so it had no .bak to compare against. Added the hashCode() patch, froze it in .fernignore, listed it in AGENTS.md, and extended RegenTypesTest.FieldsLessMessageContract to assert it. Added a post-regen sweep step to AGENTS.md so a newly generated fields-less type cannot slip through this way again. 2. The ClientOptions rationale in the previous commit message and the PR body was wrong on a point of fact. It claimed 4.18.0's getSdkVersion() helper "always resolves null" because build.gradle sets no jar manifest attributes. That is only true for Gradle builds and tests. The published artifact is built by Maven (release-please.yml runs `mvn deploy -P release`) and pom.xml's maven-jar-plugin sets addDefaultImplementationEntries=true, so the published JAR manifest does carry Implementation-Version and the generator's helper would resolve correctly there. The decision to keep the explicit literals still stands, on the accurate rationale: they are correct under Gradle, Maven, and tests alike, whereas getSdkVersion() is correct only via Maven and silently reports a stale generator-authored fallback everywhere else. release-please already lists this file in extra-files, so the markers are actively wired. Recorded in AGENTS.md. Verified: ./gradlew test compileExamples -> 112 tests, 0 failures, 1 skipped. --- .fernignore | 1 + AGENTS.md | 17 +++++++++++++---- .../listen/v2/types/ListenV2ForceEndTurn.java | 8 ++++++++ src/test/java/com/deepgram/RegenTypesTest.java | 2 ++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.fernignore b/.fernignore index 70ccf7a..d304645 100644 --- a/.fernignore +++ b/.fernignore @@ -55,6 +55,7 @@ src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java # hashCode() to each. Unfreeze and drop these patches once the generator emits a matching # equals/hashCode pair for fields-less types (tracked as an upstream Fern request). src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java +src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java diff --git a/AGENTS.md b/AGENTS.md index 8b43260..43160e1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,11 +47,11 @@ How to identify: Current temporarily frozen files: -- `src/main/java/com/deepgram/core/ClientOptions.java` - preserves release-please version markers and correct SDK header constants that Fern currently overwrites; use the standard `.bak` swap/restore workflow during regen review +- `src/main/java/com/deepgram/core/ClientOptions.java` - preserves release-please version markers and correct SDK header constants that Fern currently overwrites; use the standard `.bak` swap/restore workflow during regen review. Since generator 4.18.0 Fern emits a `getSdkVersion()` helper reading `Package.getImplementationVersion()` instead of a literal. That *does* resolve in the published artifact (CI publishes via `mvn deploy -P release`, and `pom.xml`'s maven-jar-plugin sets `addDefaultImplementationEntries=true`, so the JAR manifest carries `Implementation-Version`), but it resolves to `null` under Gradle and in tests, where it silently falls back to a hardcoded literal the generator does not keep current. We keep the explicit literals because they are correct in every context and because `.github/release-please-config.json` already lists this file in `extra-files`, so release-please bumps it alongside `pom.xml`, `build.gradle`, and `.fern/metadata.json`. Fern also emits `User-Agent` with a `com.deepgram.` prefix while leaving `X-Fern-SDK-Name` on the `com.deepgram:` Maven-coordinate form; we keep both on the colon form. - `src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java` - carries bug fixes for `maxRetries(0)` semantics ("connect once, don't retry") and a configurable `connectionTimeoutMs` field (was hardcoded 4000ms), plus an `applyOptionsOverride(...)` hook used by `TransportWebSocketFactory` to apply per-transport reconnect policy; pull this back out once the fixes are upstreamed into the Fern generator. Use the standard `.bak` swap/restore workflow during regen review. - `src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java` and `src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java` - forward-compat patch (both clients). Fern's generated `handleIncomingMessage` dispatcher routes any unrecognized message type to `onError` with "Update your SDK version...", which makes a benign new server control frame look fatal to a deployed client. Patched so the unrecognized-type branch is a no-op — the raw frame is already delivered via `onMessage(String)` earlier in the method, so consumers still see it. Mirrors the JS/Python SDKs' forward-compat behavior and is regression-guarded by `src/test/java/com/deepgram/SpeakV2ForwardCompatTest.java` and `src/test/java/com/deepgram/ListenV2ForwardCompatTest.java`. These two clients also carry the streaming query-param patches described in the next entry. Use the standard `.bak` swap/restore workflow during regen review; re-apply the no-op to both after regen, and unfreeze once the generator stops treating unknown frames as errors. - `src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java` and `src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java` (and the v2 clients above) - streaming query-param patches on the generated `connect()` builders. Two fixes: (1) multi-value serialization — array-valued params (listen: `keyterm`, `keywords`, `replace`, `search`, `tag`, `extra`, `language_hint`; speak: `tag`) were serialized with `String.valueOf(union.get())`, collapsing a `List` into one param (`keyterm=[a, b]`) instead of repeats (`keyterm=a&keyterm=b`); (2) an `additionalProperties` escape hatch — the builder exposes `additionalProperty(key, value)` for unmodeled params (e.g. `no_delay`) but `connect()` never emitted them to the URL. Both patched to route through `QueryStringMapper(arraysAsRepeats=true)`, matching the REST path. Use the standard `.bak` swap/restore workflow during regen review; re-apply after regen and unfreeze once the generator emits array params as repeats and serializes `additionalProperties` on the WS `connect()` path (tracked as an upstream Fern request). -- Fields-less message types carrying a manual `hashCode()` patch (Fern generates `equals()` but no `hashCode()` for these, violating the Object contract): `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java`, and the `AgentV1*` event types `src/main/java/com/deepgram/resources/agent/v1/types/{AgentV1ListenUpdated,AgentV1SpeakUpdated,AgentV1AgentAudioDone,AgentV1SettingsApplied,AgentV1UserStartedSpeaking,AgentV1KeepAlive,AgentV1ThinkUpdated,AgentV1PromptUpdated}.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze all of them once the generator emits a matching equals/hashCode pair for fields-less types (tracked as an upstream Fern request). +- Fields-less message types carrying a manual `hashCode()` patch (Fern generates `equals()` but no `hashCode()` for these, violating the Object contract): `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java`, `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java`, and the `AgentV1*` event types `src/main/java/com/deepgram/resources/agent/v1/types/{AgentV1ListenUpdated,AgentV1SpeakUpdated,AgentV1AgentAudioDone,AgentV1SettingsApplied,AgentV1UserStartedSpeaking,AgentV1KeepAlive,AgentV1ThinkUpdated,AgentV1PromptUpdated}.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze all of them once the generator emits a matching equals/hashCode pair for fields-less types (tracked as an upstream Fern request). - Union default-variant fix on the agent listen-provider unions: `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java`. `version` is an optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits), but Fern points `@JsonTypeInfo` `defaultImpl` at the empty-bodied `_UnknownValue`, so such a payload deserializes to an unknown variant carrying `null` — `getProvider()` returns `null` and re-serialization emits `{"provider":null}`, silently dropping the provider on the wire. Patched to `defaultImpl = V2Value` on each; guarded by `src/test/java/com/deepgram/AgentSettingsProviderDefaultTest.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze once the generator stops defaulting unions to the empty `_UnknownValue` (tracked as an upstream Fern request). ### Prepare repo for regeneration @@ -75,5 +75,14 @@ The `.bak` files are our manually patched versions protected by `.fernignore`. T 3. In `.fernignore`, replace each `.bak` path back to the original path for files that still need patches. 4. Remove `.fernignore` entries entirely for any files where the generator now produces correct output. 5. Delete all `.bak` files once review is complete. -6. Run checks (`./gradlew test compileExamples`) to verify. `test` covers unit/wire tests including the README snippet compilation; `compileExamples` separately compiles the hand-maintained `examples/` directory and catches stale API call sites that `test` alone would miss. -7. Commit as `chore: re-apply manual patches after regen` and push. +6. Sweep for **newly generated** files that need an existing patch class. Diffing `.bak` files only catches regressions in files we already froze — a brand-new generated type has no `.bak` and will slip through. At minimum, re-run the fields-less `hashCode()` sweep: + + ```sh + for f in $(grep -rl 'instanceof' src/main/java/com/deepgram --include='*.java'); do + grep -q 'public boolean equals' "$f" && ! grep -q 'public int hashCode' "$f" && echo "$f" + done + ``` + + Ignore `core/Nullable.java` and `core/NullableNonemptyFilter.java` (Jackson utility classes, not message types). Anything else in the list needs the `hashCode()` patch, a `.fernignore` entry, an entry in the list above, and a `RegenTypesTest.FieldsLessMessageContract` assertion. `ListenV2ForceEndTurn` reached `main` unpatched this way. +7. Run checks (`./gradlew test compileExamples`) to verify. `test` covers unit/wire tests including the README snippet compilation; `compileExamples` separately compiles the hand-maintained `examples/` directory and catches stale API call sites that `test` alone would miss. +8. Commit as `chore: re-apply manual patches after regen` and push. diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java index c3dbd38..a2e722a 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java @@ -36,6 +36,14 @@ public boolean equals(Object other) { return other instanceof ListenV2ForceEndTurn; } + @java.lang.Override + public int hashCode() { + // Manual patch: Fern generates equals() (all instances of this fields-less message + // are equal) but no hashCode(), violating the Object contract. Mirror equals() with a + // type-based constant hash. Remove once Fern emits a consistent equals/hashCode pair. + return getClass().hashCode(); + } + @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; diff --git a/src/test/java/com/deepgram/RegenTypesTest.java b/src/test/java/com/deepgram/RegenTypesTest.java index e6b6165..f0a2068 100644 --- a/src/test/java/com/deepgram/RegenTypesTest.java +++ b/src/test/java/com/deepgram/RegenTypesTest.java @@ -13,6 +13,7 @@ import com.deepgram.resources.agent.v1.types.AgentV1UpdateListenListenProvider; import com.deepgram.resources.agent.v1.types.AgentV1UserStartedSpeaking; import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; +import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; import com.deepgram.resources.listen.v2.types.ListenV2TurnInfoWordsItem; import com.deepgram.resources.speak.v2.types.SpeakV2Close; import com.deepgram.resources.speak.v2.types.SpeakV2Flush; @@ -125,6 +126,7 @@ void equalsHashCodeContract() { assertContract(AgentV1KeepAlive.builder().build(), AgentV1KeepAlive.builder().build()); assertContract(AgentV1ThinkUpdated.builder().build(), AgentV1ThinkUpdated.builder().build()); assertContract(AgentV1PromptUpdated.builder().build(), AgentV1PromptUpdated.builder().build()); + assertContract(ListenV2ForceEndTurn.builder().build(), ListenV2ForceEndTurn.builder().build()); } private void assertContract(Object a, Object b) { From e596be77c8b6221fb50cb21e7df4d1b8aedfb1b0 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 10:59:27 +0100 Subject: [PATCH 06/12] fix: restore speak v2 connect speed to Double, reverting the 4.18.0 retype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generator 4.18.0 retyped V2ConnectOptions.speed from Optional to the SpeakV2Speed string-literal enum. The preceding commit accepted that and updated SpeakV2ConnectWireTest to match, which was wrong — it shipped a breaking change to a parameter that went out as numeric in 0.8.0, and papered over it by editing the test rather than questioning the type. Two things establish the generated type is wrong, not merely inconvenient: - The spec contradicts itself. The sibling mid-stream SpeakV2Configure.speed is still Optional, so the same value is numeric on one message and a string enum on the other. - The server parses the numeric form and range-checks it, reporting SPEED_OUT_OF_RANGE and SPEED_INCREMENT_INVALID. A closed 7-value enum cannot express a valid in-range value it happens to omit. Restored speed to Optional, froze V2ConnectOptions in .fernignore, and reverted SpeakV2ConnectWireTest to `.speed(1.05)` so it once again asserts the same behaviour it did on main. The generated SpeakV2Speed type is left in place, unused, as documentation of the accepted values. Verified: ./gradlew test compileExamples -> 112 tests, 0 failures, 1 skipped. --- .fernignore | 10 +++++++ AGENTS.md | 1 + .../speak/v2/websocket/V2ConnectOptions.java | 30 +++++++++++++------ .../com/deepgram/SpeakV2ConnectWireTest.java | 3 +- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/.fernignore b/.fernignore index d304645..4e5b448 100644 --- a/.fernignore +++ b/.fernignore @@ -50,6 +50,16 @@ src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java +# Back-compat patch for the speak v2 connect `speed` retype. Generator 4.18.0 changed this param +# from Double to the SpeakV2Speed string-literal enum — breaking for every caller that passed a +# number in 0.8.0, and a closed 7-value enum cannot express a valid in-range value it omits. The +# spec contradicts itself: the sibling mid-stream SpeakV2Configure.speed is still Double, and the +# server parses the numeric form and range-checks it (SPEED_OUT_OF_RANGE / SPEED_INCREMENT_INVALID). +# Restored to Double; guarded by SpeakV2ConnectWireTest. Unfreeze once the spec types the connect +# `speed` as a number. The generated SpeakV2Speed type is left in place, unused, as documentation +# of the accepted values. +src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java + # Manual equals/hashCode contract fix: Fern generates equals() (all instances equal) but no # hashCode() for fields-less message types, violating the Object contract. We add a consistent # hashCode() to each. Unfreeze and drop these patches once the generator emits a matching diff --git a/AGENTS.md b/AGENTS.md index 43160e1..6d6853a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,6 +52,7 @@ Current temporarily frozen files: - `src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java` and `src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java` - forward-compat patch (both clients). Fern's generated `handleIncomingMessage` dispatcher routes any unrecognized message type to `onError` with "Update your SDK version...", which makes a benign new server control frame look fatal to a deployed client. Patched so the unrecognized-type branch is a no-op — the raw frame is already delivered via `onMessage(String)` earlier in the method, so consumers still see it. Mirrors the JS/Python SDKs' forward-compat behavior and is regression-guarded by `src/test/java/com/deepgram/SpeakV2ForwardCompatTest.java` and `src/test/java/com/deepgram/ListenV2ForwardCompatTest.java`. These two clients also carry the streaming query-param patches described in the next entry. Use the standard `.bak` swap/restore workflow during regen review; re-apply the no-op to both after regen, and unfreeze once the generator stops treating unknown frames as errors. - `src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java` and `src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java` (and the v2 clients above) - streaming query-param patches on the generated `connect()` builders. Two fixes: (1) multi-value serialization — array-valued params (listen: `keyterm`, `keywords`, `replace`, `search`, `tag`, `extra`, `language_hint`; speak: `tag`) were serialized with `String.valueOf(union.get())`, collapsing a `List` into one param (`keyterm=[a, b]`) instead of repeats (`keyterm=a&keyterm=b`); (2) an `additionalProperties` escape hatch — the builder exposes `additionalProperty(key, value)` for unmodeled params (e.g. `no_delay`) but `connect()` never emitted them to the URL. Both patched to route through `QueryStringMapper(arraysAsRepeats=true)`, matching the REST path. Use the standard `.bak` swap/restore workflow during regen review; re-apply after regen and unfreeze once the generator emits array params as repeats and serializes `additionalProperties` on the WS `connect()` path (tracked as an upstream Fern request). - Fields-less message types carrying a manual `hashCode()` patch (Fern generates `equals()` but no `hashCode()` for these, violating the Object contract): `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java`, `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java`, and the `AgentV1*` event types `src/main/java/com/deepgram/resources/agent/v1/types/{AgentV1ListenUpdated,AgentV1SpeakUpdated,AgentV1AgentAudioDone,AgentV1SettingsApplied,AgentV1UserStartedSpeaking,AgentV1KeepAlive,AgentV1ThinkUpdated,AgentV1PromptUpdated}.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze all of them once the generator emits a matching equals/hashCode pair for fields-less types (tracked as an upstream Fern request). +- `src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java` - back-compat patch for the speak v2 connect `speed` retype. Generator 4.18.0 changed the param from `Optional` to the `SpeakV2Speed` string-literal enum, which breaks every caller that passed a number in 0.8.0 and, being a closed 7-value enum, cannot express a valid in-range value the enum omits. The spec contradicts itself here: the sibling mid-stream `SpeakV2Configure.speed` is still `Optional`, and the server parses the numeric form and range-checks it (`SPEED_OUT_OF_RANGE`, `SPEED_INCREMENT_INVALID`). Restored to `Optional`; guarded by `src/test/java/com/deepgram/SpeakV2ConnectWireTest.java`. The generated `SpeakV2Speed` type is left in place, unused, as documentation of the accepted values. Use the standard `.bak` swap/restore workflow during regen review; unfreeze once the spec types the connect `speed` as a number (tracked as an upstream Fern request). - Union default-variant fix on the agent listen-provider unions: `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java`. `version` is an optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits), but Fern points `@JsonTypeInfo` `defaultImpl` at the empty-bodied `_UnknownValue`, so such a payload deserializes to an unknown variant carrying `null` — `getProvider()` returns `null` and re-serialization emits `{"provider":null}`, silently dropping the provider on the wire. Patched to `defaultImpl = V2Value` on each; guarded by `src/test/java/com/deepgram/AgentSettingsProviderDefaultTest.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze once the generator stops defaulting unions to the empty `_UnknownValue` (tracked as an upstream Fern request). ### Prepare repo for regeneration diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java index ca66de0..a35a7f7 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java +++ b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java @@ -7,7 +7,6 @@ import com.deepgram.types.SpeakV2Encoding; import com.deepgram.types.SpeakV2MipOptOut; import com.deepgram.types.SpeakV2SampleRate; -import com.deepgram.types.SpeakV2Speed; import com.deepgram.types.SpeakV2Tag; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -32,7 +31,7 @@ public final class V2ConnectOptions { private final Optional sampleRate; - private final Optional speed; + private final Optional speed; private final Optional expressivity; @@ -46,7 +45,7 @@ private V2ConnectOptions( String model, Optional encoding, Optional sampleRate, - Optional speed, + Optional speed, Optional expressivity, Optional mipOptOut, Optional tag, @@ -76,8 +75,21 @@ public Optional getSampleRate() { return sampleRate; } + /** + * Speech-rate multiplier. {@code 1.0} is the model's nominal rate; lower is slower. Accepted values run + * {@code 0.85} to {@code 1.15} in {@code 0.05} increments. A value outside that range is rejected with + * {@code SPEED_OUT_OF_RANGE}; a value inside it but off the increment with {@code SPEED_INCREMENT_INVALID}. + * + *

Manual patch (2026-08-19 regen): generator 4.18.0 retyped this from {@code Double} to the + * {@code SpeakV2Speed} string-literal enum, a breaking change to a parameter that shipped as numeric in + * 0.8.0. The spec contradicts itself here — the sibling mid-stream {@code SpeakV2Configure.speed} is still + * {@code Optional} — and the server parses the numeric form and range-checks it, so the closed + * string enum both breaks callers and cannot express a valid in-range value the enum omits. Restored to + * {@code Double}. Frozen in {@code .fernignore}; unfreeze once the spec types the connect + * {@code speed} as a number. + */ @JsonProperty("speed") - public Optional getSpeed() { + public Optional getSpeed() { return speed; } @@ -153,9 +165,9 @@ public interface _FinalStage { _FinalStage sampleRate(SpeakV2SampleRate sampleRate); - _FinalStage speed(Optional speed); + _FinalStage speed(Optional speed); - _FinalStage speed(SpeakV2Speed speed); + _FinalStage speed(Double speed); _FinalStage expressivity(Optional expressivity); @@ -180,7 +192,7 @@ public static final class Builder implements ModelStage, _FinalStage { private Optional expressivity = Optional.empty(); - private Optional speed = Optional.empty(); + private Optional speed = Optional.empty(); private Optional sampleRate = Optional.empty(); @@ -250,14 +262,14 @@ public _FinalStage expressivity(Optional expressivity) { } @java.lang.Override - public _FinalStage speed(SpeakV2Speed speed) { + public _FinalStage speed(Double speed) { this.speed = Optional.ofNullable(speed); return this; } @java.lang.Override @JsonSetter(value = "speed", nulls = Nulls.SKIP) - public _FinalStage speed(Optional speed) { + public _FinalStage speed(Optional speed) { this.speed = speed; return this; } diff --git a/src/test/java/com/deepgram/SpeakV2ConnectWireTest.java b/src/test/java/com/deepgram/SpeakV2ConnectWireTest.java index 37e57db..5d899ef 100644 --- a/src/test/java/com/deepgram/SpeakV2ConnectWireTest.java +++ b/src/test/java/com/deepgram/SpeakV2ConnectWireTest.java @@ -8,7 +8,6 @@ import com.deepgram.resources.speak.v2.types.SpeakV2InterruptPlaybackOffset; import com.deepgram.resources.speak.v2.websocket.V2ConnectOptions; import com.deepgram.resources.speak.v2.websocket.V2WebSocketClient; -import com.deepgram.types.SpeakV2Speed; import com.deepgram.types.SpeakV2Tag; import java.util.List; import java.util.concurrent.BlockingQueue; @@ -99,7 +98,7 @@ void tagStringSentAsOneParam() throws Exception { void speedPresentWhenSet() throws Exception { HttpUrl url = connectAndCaptureUrl(V2ConnectOptions.builder() .model("flux-alexis-en") - .speed(SpeakV2Speed.ONE_POINT_ZERO_FIVE) + .speed(1.05) .build()); assertThat(url.queryParameter("speed")).isEqualTo("1.05"); From 98ce308cbf9db7fdef231502ba31ece2245b7a0b Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 11:06:30 +0100 Subject: [PATCH 07/12] docs: add v0.8 to v0.9 migration guide for the regen renames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the breaking changes rather than shimming them, matching the convention this repo used for 0.8.0: the renames ship, and the migration path is written down. Covers the nine provider/agent-history type renames, the two removed duplicate agent conversation-history leaf types, and the removal of DeepgramModel.FLUX_RENEE_EN. Every change is source-level with identical wire values, so the guide says so explicitly — a clean recompile means the upgrade is done and no request payloads change. Also documents what deliberately did NOT change: V2ConnectOptions.speed stays Optional despite the generator retyping it, so readers who see the generated SpeakV2Speed type do not assume their speed calls need updating. Additive coverage: sendForceEndTurn (flagged as requiring server-side enablement and not yet generally available), ListenV2TurnInfo.getTrigger(), the Listen V1 diarize_info metadata and per-word speaker_confidence, and the 25 new DeepgramModel voice constants. Linked from the README migration index as current. --- README.md | 3 +- docs/Migrating-v0.8-to-v0.9.md | 263 +++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 docs/Migrating-v0.8-to-v0.9.md diff --git a/README.md b/README.md index 37578f6..505e907 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ You can learn more about the Deepgram API at [developers.deepgram.com](https://d ### Migrating Between Versions -- [v0.7 to v0.8](./docs/Migrating-v0.7-to-v0.8.md) (current) +- [v0.8 to v0.9](./docs/Migrating-v0.8-to-v0.9.md) (current) +- [v0.7 to v0.8](./docs/Migrating-v0.7-to-v0.8.md) - [v0.6 to v0.7](./docs/Migrating-v0.6-to-v0.7.md) - [v0.5 to v0.6](./docs/Migrating-v0.5-to-v0.6.md) - [v0.3 to v0.4](./docs/Migrating-v0.3-to-v0.4.md) diff --git a/docs/Migrating-v0.8-to-v0.9.md b/docs/Migrating-v0.8-to-v0.9.md new file mode 100644 index 0000000..34b1112 --- /dev/null +++ b/docs/Migrating-v0.8-to-v0.9.md @@ -0,0 +1,263 @@ +# v0.8 to v0.9 Migration Guide + +This guide helps you migrate from Deepgram Java SDK v0.8.x to v0.9.0. The `0.9.0` release is still pre-`1.0`, and it ships a set of breaking source changes from the latest SDK regeneration along with additive features (Listen V2 force-end-turn and turn triggers, Listen V1 diarization detail, and 25 new Deepgram TTS voice constants). + +All of the breaking changes are **source/compile-time only** — they are type and constant renames that follow the API definition. Every wire value is unchanged, so on-the-wire payloads for existing requests are byte-identical and no server-side behavior changes with this upgrade. In practice, migrating means updating imports and type references, then recompiling. + +The breaking changes are: + +1. **Nine provider and agent-history types renamed** — the generator dropped the `*ThinkProvider*` / `*SpeakProvider*` prefixes and consolidated the agent conversation-history leaf types. Wire values are identical. +2. **Two duplicate agent conversation-history types removed** — `AgentV1SettingsAgentContextContextMessagesItemContentRole` and `AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem` are replaced by the canonical `ConversationHistoryMessageRole` and `FunctionCallHistoryMessageFunctionCallsItem`. +3. **`DeepgramModel.FLUX_RENEE_EN` removed** — the `flux-renee-en` voice is no longer in the model enum. + +> **Note on `speak.v2` `connect(speed = ...)`:** the generator retyped this parameter from `Double` to a closed string-literal enum in this cycle. That is **not** shipped here — the SDK keeps `Optional`, so your existing `speed` calls continue to compile and behave identically. See [Speak V2 Connect Speed](#speak-v2-connect-speed). + +## Table of Contents + +- [Installation](#installation) +- [Configuration Changes](#configuration-changes) +- [Authentication Changes](#authentication-changes) +- [Type Changes](#type-changes) + - [Provider Type Renames](#provider-type-renames) + - [Agent Conversation-History Consolidation](#agent-conversation-history-consolidation) + - [Removed Voice Constant](#removed-voice-constant) + - [Speak V2 Connect Speed](#speak-v2-connect-speed) +- [New Features in v0.9.0](#new-features-in-v090) + - [Listen V2 Force-End-Turn](#listen-v2-force-end-turn) + - [Listen V2 Turn Trigger](#listen-v2-turn-trigger) + - [Listen V1 Diarization Detail](#listen-v1-diarization-detail) + - [New Deepgram Voice Constants](#new-deepgram-voice-constants) +- [Breaking Changes Summary](#breaking-changes-summary) + +## Installation + +Upgrade to `0.9.0` with Gradle or Maven. + +**Gradle** + +```groovy +dependencies { + implementation 'com.deepgram:deepgram-java-sdk:0.9.0' +} +``` + +**Maven** + +```xml + + com.deepgram + deepgram-java-sdk + 0.9.0 + +``` + +## Configuration Changes + +No changes. Existing `DeepgramClient.builder()` usage — including the retry tuning and transport options added in `0.8.0` — works unchanged. + +## Authentication Changes + +No changes. API key, access token, and session ID configuration all work the same as in `0.8.x`. + +## Type Changes + +### Provider Type Renames + +Seven provider model/voice/version types lost their role prefixes, and two agent conversation-history types were renamed. **The constant names and their wire values are unchanged** — only the enclosing type name differs, so migration is a find-and-replace on imports and type references. + +| v0.8.x | v0.9.0 | +| --- | --- | +| `AnthropicThinkProviderModel` | `AnthropicModel` | +| `CartesiaSpeakProviderModelId` | `CartesiaModelId` | +| `CartesiaSpeakProviderVoice` | `CartesiaVoice` | +| `DeepgramSpeakProviderModel` | `DeepgramModel` | +| `GoogleThinkProviderModel` | `GoogleModel` | +| `GoogleThinkProviderVersion` | `GoogleVersion` | +| `GroqThinkProviderReasoningMode` | `GroqReasoningMode` | +| `AgentV1HistoryContentRole` | `ConversationHistoryMessageRole` | +| `AgentV1HistoryFunctionCallsFunctionCallsItem` | `FunctionCallHistoryMessageFunctionCallsItem` | + +All nine live in `com.deepgram.types` except the last two, which live in `com.deepgram.resources.agent.v1.types`. + +**v0.8.x** + +```java +import com.deepgram.types.AnthropicThinkProviderModel; +import com.deepgram.types.DeepgramSpeakProviderModel; +import com.deepgram.types.GoogleThinkProviderModel; +import com.deepgram.types.GoogleThinkProviderVersion; + +Anthropic anthropic = Anthropic.builder() + .model(AnthropicThinkProviderModel.CLAUDE_SONNET420250514) + .build(); + +Deepgram speak = Deepgram.builder() + .model(DeepgramSpeakProviderModel.AURA2ASTERIA_EN) + .build(); + +Google google = Google.builder() + .model(GoogleThinkProviderModel.GEMINI25FLASH) + .version(GoogleThinkProviderVersion.V1BETA) + .build(); + +Optional version = google.getVersion(); +``` + +**v0.9.0** + +```java +import com.deepgram.types.AnthropicModel; +import com.deepgram.types.DeepgramModel; +import com.deepgram.types.GoogleModel; +import com.deepgram.types.GoogleVersion; + +Anthropic anthropic = Anthropic.builder() + .model(AnthropicModel.CLAUDE_SONNET420250514) + .build(); + +Deepgram speak = Deepgram.builder() + .model(DeepgramModel.AURA2ASTERIA_EN) + .build(); + +Google google = Google.builder() + .model(GoogleModel.GEMINI25FLASH) + .version(GoogleVersion.V1BETA) + .build(); + +Optional version = google.getVersion(); +``` + +Note that the getter return types change too, so a declared `Optional` must be updated — not just the builder call. + +`OpenAiThinkProvider` and `OpenAiThinkProviderModel` are **not** renamed and keep their existing names. + +### Agent Conversation-History Consolidation + +`0.8.x` carried four leaf types for the agent conversation-history messages, in two duplicate pairs. `0.9.0` collapses each pair into one canonical type. The removed types were structurally identical to their replacements — same constants, same wire values (`"user"` / `"assistant"`). + +| v0.8.x | v0.9.0 | +| --- | --- | +| `AgentV1SettingsAgentContextContextMessagesItemContentRole` (removed) | `ConversationHistoryMessageRole` | +| `AgentV1HistoryContentRole` (renamed) | `ConversationHistoryMessageRole` | +| `AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem` (removed) | `FunctionCallHistoryMessageFunctionCallsItem` | +| `AgentV1HistoryFunctionCallsFunctionCallsItem` (renamed) | `FunctionCallHistoryMessageFunctionCallsItem` | + +This changes the field types on `ConversationHistoryMessage` and `FunctionCallHistoryMessage`: + +**v0.8.x** + +```java +ConversationHistoryMessage msg = ConversationHistoryMessage.builder() + .role(AgentV1SettingsAgentContextContextMessagesItemContentRole.USER) + .content("Hello") + .build(); +``` + +**v0.9.0** + +```java +import com.deepgram.resources.agent.v1.types.ConversationHistoryMessageRole; + +ConversationHistoryMessage msg = ConversationHistoryMessage.builder() + .role(ConversationHistoryMessageRole.USER) + .content("Hello") + .build(); +``` + +### Removed Voice Constant + +`DeepgramModel.FLUX_RENEE_EN` (wire value `flux-renee-en`) is no longer part of the model enum. If you referenced it, pick another Flux voice — see [New Deepgram Voice Constants](#new-deepgram-voice-constants) for what was added this cycle. + +`DeepgramModel` remains a forward-compatible enum, so if you need to send a value the current SDK does not model, `DeepgramModel.valueOf("some-model-name")` still works. + +### Speak V2 Connect Speed + +**No change required — this parameter is unchanged from `0.8.x`.** It is called out only because the generator attempted to change it and the SDK deliberately does not ship that change. + +`speak.v2` `V2ConnectOptions.speed` remains `Optional`: + +```java +V2ConnectOptions.builder() + .model("flux-alexis-en") + .speed(1.05) + .build(); +``` + +The generator retyped this to a closed seven-value string enum in this cycle. The SDK keeps `Double`, because the API contract is numeric: the mid-stream `SpeakV2Configure.speed` field is numeric on the same connection, and the server parses the number and range-checks it — reporting `SPEED_OUT_OF_RANGE` for a value outside `0.85`–`1.15` and `SPEED_INCREMENT_INVALID` for a value inside the range but off the `0.05` increment. A closed enum could not express a valid in-range value it happened to omit. + +## New Features in v0.9.0 + +### Listen V2 Force-End-Turn + +Listen V2 gains a control message to end the current turn on demand: + +```java +V2WebSocketClient wsClient = client.listen().v2().v2WebSocket(); +// ... connect and stream ... +wsClient.sendForceEndTurn(ListenV2ForceEndTurn.builder().build()); +``` + +**This requires server-side enablement and is not yet generally available.** The typed surface ships now so callers are ready, but do not depend on it until the feature is enabled for your deployment — sending it before then is rejected by the server. + +### Listen V2 Turn Trigger + +`ListenV2TurnInfo` gains `getTrigger()`, returning `Optional`, which identifies what ended a turn (for example `model`, `manual`, or `timeout`). It is typed as an open `String` so new server-side trigger values do not break deployed clients. + +### Listen V1 Diarization Detail + +Pre-recorded Listen V1 responses now expose which diarizer ran, via `getDiarizeInfo()` on the response metadata: + +```java +Optional info = response.getMetadata().getDiarizeInfo(); +info.ifPresent(i -> System.out.println(i.getArch() + " " + i.getModelUuid())); +``` + +`ListenV1ResponseMetadataDiarizeInfo` and `ListenV1ResultsMetadataDiarizeInfo` each carry `getModelUuid()` and `getArch()`. Both are present only when diarization is enabled. + +Pre-recorded words items also carry `getSpeakerConfidence()` (`Optional`) alongside the existing `getSpeaker()`. Per-word `speaker_confidence` is returned for pre-recorded transcription only, not for streaming. + +### New Deepgram Voice Constants + +`DeepgramModel` gained 25 constants this cycle, including the Flux TTS voices (`FLUX_KELSEY_EN`, `FLUX_SIENNA_EN`, `FLUX_MEENA_EN`, `FLUX_CONOR_EN`, `FLUX_BROOKE_EN`, `FLUX_RUFUS_EN`, `FLUX_WES_EN`, and others) as well as additional Aura 2 voices. + +## Breaking Changes Summary + +### Changed Type Names + +| Old | New | +| --- | --- | +| `AnthropicThinkProviderModel` | `AnthropicModel` | +| `CartesiaSpeakProviderModelId` | `CartesiaModelId` | +| `CartesiaSpeakProviderVoice` | `CartesiaVoice` | +| `DeepgramSpeakProviderModel` | `DeepgramModel` | +| `GoogleThinkProviderModel` | `GoogleModel` | +| `GoogleThinkProviderVersion` | `GoogleVersion` | +| `GroqThinkProviderReasoningMode` | `GroqReasoningMode` | +| `AgentV1HistoryContentRole` | `ConversationHistoryMessageRole` | +| `AgentV1SettingsAgentContextContextMessagesItemContentRole` | `ConversationHistoryMessageRole` | +| `AgentV1HistoryFunctionCallsFunctionCallsItem` | `FunctionCallHistoryMessageFunctionCallsItem` | +| `AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem` | `FunctionCallHistoryMessageFunctionCallsItem` | + +### Removed + +- `DeepgramModel.FLUX_RENEE_EN` + +### Unchanged Despite Generator Churn + +- `V2ConnectOptions.speed` stays `Optional` + +### New Features + +- `listen.v2` `sendForceEndTurn(ListenV2ForceEndTurn)` — requires server-side enablement, not yet generally available +- `ListenV2TurnInfo.getTrigger()` +- `ListenV1ResponseMetadata.getDiarizeInfo()` / `ListenV1ResultsMetadata.getDiarizeInfo()` +- `getSpeakerConfidence()` on pre-recorded words items +- 25 new `DeepgramModel` constants + +### Migration Checklist + +1. Bump the dependency to `0.9.0`. +2. Find-and-replace the nine renamed type names in imports and type references, including declared getter return types such as `Optional`. +3. Replace `AgentV1SettingsAgentContextContextMessagesItem*` references with `ConversationHistoryMessageRole` / `FunctionCallHistoryMessageFunctionCallsItem`. +4. Replace any use of `DeepgramModel.FLUX_RENEE_EN`. +5. Recompile. Because every change is a source-level rename with identical wire values, a clean compile means the upgrade is complete — no request payloads change. From 700ff71a57defa239b5bdd47f66ee3cd77d3f29d Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 11:52:39 +0100 Subject: [PATCH 08/12] fix: restore DeepgramModel.FLUX_RENEE_EN, dropped by 4.18.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generator 4.18.0 removed this constant. The preceding migration guide documented that as a retirement and told readers to "pick another Flux voice" — that was wrong, and verifying against the live API showed why. The voice is fully functional: - POST /v2/speak?model=flux-renee-en returns 200 with a valid 3.1s MP3 (MPEG layer III, 48 kbps, 24 kHz mono — Flux TTS's output format). - The name resolves in the server's model registry, not by prefix: an invented flux-bogus-nonexistent-en is rejected with INVALID_QUERY_PARAMETER, while flux-renee-en is accepted. - Internally it appears in a July 2026 full regression sweep across all speakers and in current voice rosters, with no deprecation or sunset language. So the removal is a spec regression, not a retirement, and dropping the constant would break 0.8.0 callers for nothing. Restored all five touchpoints (the constant, the Value enum entry, the visit() case, the valueOf() case, and the Visitor method) and froze DeepgramModel in .fernignore. Guarded by a new RegenTypesTest case asserting the wire value, valueOf() resolution, and JSON round-trip. The 25 constants 4.18.0 added are all retained: the enum now has 100, with nothing removed relative to 0.8.0. Freezing this file carries a risk the other frozen files do not — it receives frequent additive spec changes, so restoring its .bak wholesale on a future regen would silently drop new voices. Both .fernignore and AGENTS.md carry an explicit warning to diff and carry additions forward instead. Migration guide updated: FLUX_RENEE_EN moves out of the breaking-changes list into the "unchanged despite generator churn" section alongside the speed restoration, leaving two documented breaking changes. Verified: ./gradlew test compileExamples -> 113 tests, 0 failures, 1 skipped. --- .fernignore | 12 +++++++++ AGENTS.md | 1 + docs/Migrating-v0.8-to-v0.9.md | 27 ++++++++++--------- .../com/deepgram/types/DeepgramModel.java | 20 ++++++++++++++ .../java/com/deepgram/RegenTypesTest.java | 20 ++++++++++++++ 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/.fernignore b/.fernignore index 4e5b448..94657af 100644 --- a/.fernignore +++ b/.fernignore @@ -60,6 +60,18 @@ src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java # of the accepted values. src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java +# Restores the FLUX_RENEE_EN constant that generator 4.18.0 dropped. The voice is live: +# POST /v2/speak?model=flux-renee-en returns 200 with valid audio, and the name resolves in the +# server's model registry (an invented flux-* name is rejected with INVALID_QUERY_PARAMETER), so the +# removal is a spec regression, not a retirement. Dropping the constant would break 0.8.0 callers +# for no reason. Remove the patch and unfreeze once the spec lists the voice again. +# +# WARNING: unlike the other temporarily frozen files, this one receives frequent *additive* spec +# changes -- 4.18.0 alone added 25 constants. On the next regen do NOT restore the .bak wholesale: +# diff it against the newly generated file, carry forward every new voice, and re-apply only the +# FLUX_RENEE_EN touchpoints (constant, Value enum, visit() case, valueOf() case, Visitor method). +src/main/java/com/deepgram/types/DeepgramModel.java + # Manual equals/hashCode contract fix: Fern generates equals() (all instances equal) but no # hashCode() for fields-less message types, violating the Object contract. We add a consistent # hashCode() to each. Unfreeze and drop these patches once the generator emits a matching diff --git a/AGENTS.md b/AGENTS.md index 6d6853a..ece5aae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -53,6 +53,7 @@ Current temporarily frozen files: - `src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java` and `src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java` (and the v2 clients above) - streaming query-param patches on the generated `connect()` builders. Two fixes: (1) multi-value serialization — array-valued params (listen: `keyterm`, `keywords`, `replace`, `search`, `tag`, `extra`, `language_hint`; speak: `tag`) were serialized with `String.valueOf(union.get())`, collapsing a `List` into one param (`keyterm=[a, b]`) instead of repeats (`keyterm=a&keyterm=b`); (2) an `additionalProperties` escape hatch — the builder exposes `additionalProperty(key, value)` for unmodeled params (e.g. `no_delay`) but `connect()` never emitted them to the URL. Both patched to route through `QueryStringMapper(arraysAsRepeats=true)`, matching the REST path. Use the standard `.bak` swap/restore workflow during regen review; re-apply after regen and unfreeze once the generator emits array params as repeats and serializes `additionalProperties` on the WS `connect()` path (tracked as an upstream Fern request). - Fields-less message types carrying a manual `hashCode()` patch (Fern generates `equals()` but no `hashCode()` for these, violating the Object contract): `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java`, `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java`, and the `AgentV1*` event types `src/main/java/com/deepgram/resources/agent/v1/types/{AgentV1ListenUpdated,AgentV1SpeakUpdated,AgentV1AgentAudioDone,AgentV1SettingsApplied,AgentV1UserStartedSpeaking,AgentV1KeepAlive,AgentV1ThinkUpdated,AgentV1PromptUpdated}.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze all of them once the generator emits a matching equals/hashCode pair for fields-less types (tracked as an upstream Fern request). - `src/main/java/com/deepgram/resources/speak/v2/websocket/V2ConnectOptions.java` - back-compat patch for the speak v2 connect `speed` retype. Generator 4.18.0 changed the param from `Optional` to the `SpeakV2Speed` string-literal enum, which breaks every caller that passed a number in 0.8.0 and, being a closed 7-value enum, cannot express a valid in-range value the enum omits. The spec contradicts itself here: the sibling mid-stream `SpeakV2Configure.speed` is still `Optional`, and the server parses the numeric form and range-checks it (`SPEED_OUT_OF_RANGE`, `SPEED_INCREMENT_INVALID`). Restored to `Optional`; guarded by `src/test/java/com/deepgram/SpeakV2ConnectWireTest.java`. The generated `SpeakV2Speed` type is left in place, unused, as documentation of the accepted values. Use the standard `.bak` swap/restore workflow during regen review; unfreeze once the spec types the connect `speed` as a number (tracked as an upstream Fern request). +- `src/main/java/com/deepgram/types/DeepgramModel.java` - restores the `FLUX_RENEE_EN` constant that generator 4.18.0 dropped. The voice is live: `POST /v2/speak?model=flux-renee-en` returns 200 with valid audio, and the name resolves in the server's model registry (an invented `flux-*` name is rejected with `INVALID_QUERY_PARAMETER`), so the removal is a spec regression rather than a retirement, and dropping the constant would break 0.8.0 callers for nothing. Five touchpoints: the constant, the `Value` enum entry, the `visit()` case, the `valueOf()` case, and the `Visitor` method. **This file is unlike the other temporarily frozen ones — it receives frequent additive spec changes (4.18.0 alone added 25 constants), so on the next regen do NOT restore the `.bak` wholesale.** Diff the `.bak` against the newly generated file, carry forward every new voice, and re-apply only the `FLUX_RENEE_EN` touchpoints. Drop the patch and unfreeze once the spec lists the voice again (tracked as an upstream spec request). - Union default-variant fix on the agent listen-provider unions: `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java`. `version` is an optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits), but Fern points `@JsonTypeInfo` `defaultImpl` at the empty-bodied `_UnknownValue`, so such a payload deserializes to an unknown variant carrying `null` — `getProvider()` returns `null` and re-serialization emits `{"provider":null}`, silently dropping the provider on the wire. Patched to `defaultImpl = V2Value` on each; guarded by `src/test/java/com/deepgram/AgentSettingsProviderDefaultTest.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze once the generator stops defaulting unions to the empty `_UnknownValue` (tracked as an upstream Fern request). ### Prepare repo for regeneration diff --git a/docs/Migrating-v0.8-to-v0.9.md b/docs/Migrating-v0.8-to-v0.9.md index 34b1112..ac0d535 100644 --- a/docs/Migrating-v0.8-to-v0.9.md +++ b/docs/Migrating-v0.8-to-v0.9.md @@ -1,14 +1,15 @@ # v0.8 to v0.9 Migration Guide -This guide helps you migrate from Deepgram Java SDK v0.8.x to v0.9.0. The `0.9.0` release is still pre-`1.0`, and it ships a set of breaking source changes from the latest SDK regeneration along with additive features (Listen V2 force-end-turn and turn triggers, Listen V1 diarization detail, and 25 new Deepgram TTS voice constants). +This guide helps you migrate from Deepgram Java SDK v0.8.x to v0.9.0. The `0.9.0` release is still pre-`1.0`, and it ships two breaking source changes from the latest SDK regeneration along with additive features (Listen V2 force-end-turn and turn triggers, Listen V1 diarization detail, and 25 new Deepgram TTS voice constants). -All of the breaking changes are **source/compile-time only** — they are type and constant renames that follow the API definition. Every wire value is unchanged, so on-the-wire payloads for existing requests are byte-identical and no server-side behavior changes with this upgrade. In practice, migrating means updating imports and type references, then recompiling. +Both breaking changes are **source/compile-time only** — they are type renames that follow the API definition. Every wire value is unchanged, so on-the-wire payloads for existing requests are byte-identical and no server-side behavior changes with this upgrade. In practice, migrating means updating imports and type references, then recompiling. -The breaking changes are: +The two breaking changes are: 1. **Nine provider and agent-history types renamed** — the generator dropped the `*ThinkProvider*` / `*SpeakProvider*` prefixes and consolidated the agent conversation-history leaf types. Wire values are identical. 2. **Two duplicate agent conversation-history types removed** — `AgentV1SettingsAgentContextContextMessagesItemContentRole` and `AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem` are replaced by the canonical `ConversationHistoryMessageRole` and `FunctionCallHistoryMessageFunctionCallsItem`. -3. **`DeepgramModel.FLUX_RENEE_EN` removed** — the `flux-renee-en` voice is no longer in the model enum. + +The generator also attempted two changes this release that the SDK deliberately does **not** ship, because both were regressions against a live API: it retyped `speak.v2` `connect(speed = ...)` from `Double` to a closed string enum, and it dropped the `DeepgramModel.FLUX_RENEE_EN` constant. Both are patched back, so no action is needed for either — see [Speak V2 Connect Speed](#speak-v2-connect-speed) and [Voice Constants](#voice-constants). > **Note on `speak.v2` `connect(speed = ...)`:** the generator retyped this parameter from `Double` to a closed string-literal enum in this cycle. That is **not** shipped here — the SDK keeps `Optional`, so your existing `speed` calls continue to compile and behave identically. See [Speak V2 Connect Speed](#speak-v2-connect-speed). @@ -20,7 +21,7 @@ The breaking changes are: - [Type Changes](#type-changes) - [Provider Type Renames](#provider-type-renames) - [Agent Conversation-History Consolidation](#agent-conversation-history-consolidation) - - [Removed Voice Constant](#removed-voice-constant) + - [Voice Constants](#voice-constants) - [Speak V2 Connect Speed](#speak-v2-connect-speed) - [New Features in v0.9.0](#new-features-in-v090) - [Listen V2 Force-End-Turn](#listen-v2-force-end-turn) @@ -164,9 +165,11 @@ ConversationHistoryMessage msg = ConversationHistoryMessage.builder() .build(); ``` -### Removed Voice Constant +### Voice Constants + +**No change required.** `DeepgramModel.FLUX_RENEE_EN` is still present, and 25 constants were added — see [New Deepgram Voice Constants](#new-deepgram-voice-constants). -`DeepgramModel.FLUX_RENEE_EN` (wire value `flux-renee-en`) is no longer part of the model enum. If you referenced it, pick another Flux voice — see [New Deepgram Voice Constants](#new-deepgram-voice-constants) for what was added this cycle. +This is called out only because the generator dropped `FLUX_RENEE_EN` in this cycle and the SDK deliberately does not ship that removal. The voice is live: `POST /v2/speak?model=flux-renee-en` returns `200` with valid audio, and the name resolves in the server's model registry (an invented `flux-*` name is rejected with `INVALID_QUERY_PARAMETER`). The constant is restored and frozen, so existing references keep compiling. `DeepgramModel` remains a forward-compatible enum, so if you need to send a value the current SDK does not model, `DeepgramModel.valueOf("some-model-name")` still works. @@ -238,13 +241,12 @@ Pre-recorded words items also carry `getSpeakerConfidence()` (`Optional`) | `AgentV1HistoryFunctionCallsFunctionCallsItem` | `FunctionCallHistoryMessageFunctionCallsItem` | | `AgentV1SettingsAgentContextContextMessagesItemFunctionCallsFunctionCallsItem` | `FunctionCallHistoryMessageFunctionCallsItem` | -### Removed - -- `DeepgramModel.FLUX_RENEE_EN` - ### Unchanged Despite Generator Churn +Both were changed by the generator this cycle; the SDK patches them back, so no action is needed. + - `V2ConnectOptions.speed` stays `Optional` +- `DeepgramModel.FLUX_RENEE_EN` is retained ### New Features @@ -259,5 +261,4 @@ Pre-recorded words items also carry `getSpeakerConfidence()` (`Optional`) 1. Bump the dependency to `0.9.0`. 2. Find-and-replace the nine renamed type names in imports and type references, including declared getter return types such as `Optional`. 3. Replace `AgentV1SettingsAgentContextContextMessagesItem*` references with `ConversationHistoryMessageRole` / `FunctionCallHistoryMessageFunctionCallsItem`. -4. Replace any use of `DeepgramModel.FLUX_RENEE_EN`. -5. Recompile. Because every change is a source-level rename with identical wire values, a clean compile means the upgrade is complete — no request payloads change. +4. Recompile. Because every change is a source-level rename with identical wire values, a clean compile means the upgrade is complete — no request payloads change. diff --git a/src/main/java/com/deepgram/types/DeepgramModel.java b/src/main/java/com/deepgram/types/DeepgramModel.java index fc7c08c..16d776e 100644 --- a/src/main/java/com/deepgram/types/DeepgramModel.java +++ b/src/main/java/com/deepgram/types/DeepgramModel.java @@ -13,6 +13,14 @@ public final class DeepgramModel { public static final DeepgramModel FLUX_KELSEY_EN = new DeepgramModel(Value.FLUX_KELSEY_EN, "flux-kelsey-en"); + /** + * Manual patch: generator 4.18.0 dropped this constant, but the voice is live — {@code + * POST /v2/speak?model=flux-renee-en} returns 200 with valid audio, and the model name resolves in the + * server's registry (an invented {@code flux-*} name is rejected with {@code INVALID_QUERY_PARAMETER}). + * Restored so 0.8.0 callers keep compiling. Frozen in {@code .fernignore}; drop once the spec lists it again. + */ + public static final DeepgramModel FLUX_RENEE_EN = new DeepgramModel(Value.FLUX_RENEE_EN, "flux-renee-en"); + public static final DeepgramModel FLUX_SIENNA_EN = new DeepgramModel(Value.FLUX_SIENNA_EN, "flux-sienna-en"); public static final DeepgramModel AURA2HARMONIA_EN = @@ -251,6 +259,9 @@ public T visit(Visitor visitor) { return visitor.visitAura2HeraEn(); case FLUX_KELSEY_EN: return visitor.visitFluxKelseyEn(); + // Manual patch: see FLUX_RENEE_EN above. + case FLUX_RENEE_EN: + return visitor.visitFluxReneeEn(); case FLUX_SIENNA_EN: return visitor.visitFluxSiennaEn(); case AURA2HARMONIA_EN: @@ -458,6 +469,9 @@ public static DeepgramModel valueOf(String value) { return AURA2HERA_EN; case "flux-kelsey-en": return FLUX_KELSEY_EN; + // Manual patch: see FLUX_RENEE_EN above. + case "flux-renee-en": + return FLUX_RENEE_EN; case "flux-sienna-en": return FLUX_SIENNA_EN; case "aura-2-harmonia-en": @@ -840,6 +854,9 @@ public enum Value { FLUX_PRIYA_EN, + // Manual patch: see FLUX_RENEE_EN above. + FLUX_RENEE_EN, + FLUX_RUFUS_EN, FLUX_SEAN_EN, @@ -1042,6 +1059,9 @@ public interface Visitor { T visitFluxPriyaEn(); + // Manual patch: see FLUX_RENEE_EN above. + T visitFluxReneeEn(); + T visitFluxRufusEn(); T visitFluxSeanEn(); diff --git a/src/test/java/com/deepgram/RegenTypesTest.java b/src/test/java/com/deepgram/RegenTypesTest.java index f0a2068..e3a4016 100644 --- a/src/test/java/com/deepgram/RegenTypesTest.java +++ b/src/test/java/com/deepgram/RegenTypesTest.java @@ -18,6 +18,7 @@ import com.deepgram.resources.speak.v2.types.SpeakV2Close; import com.deepgram.resources.speak.v2.types.SpeakV2Flush; import com.deepgram.types.DeepgramListenProviderV2; +import com.deepgram.types.DeepgramModel; import com.deepgram.types.Google; import com.deepgram.types.GoogleModel; import com.deepgram.types.GoogleVersion; @@ -155,6 +156,25 @@ void languageHintsRoundTrip() throws Exception { } } + @Nested + @DisplayName("DeepgramModel.FLUX_RENEE_EN: manually restored constant") + class FluxReneeConstant { + + @Test + @DisplayName("resolves, carries the right wire value, and round-trips") + void reneeConstantSurvives() throws Exception { + // Generator 4.18.0 dropped this constant, but the voice is live on /v2/speak. The patch + // restores all five touchpoints; this guards a future regen silently dropping it again. + assertThat(DeepgramModel.FLUX_RENEE_EN.toString()).isEqualTo("flux-renee-en"); + assertThat(DeepgramModel.FLUX_RENEE_EN.getEnumValue()).isEqualTo(DeepgramModel.Value.FLUX_RENEE_EN); + assertThat(DeepgramModel.valueOf("flux-renee-en")).isEqualTo(DeepgramModel.FLUX_RENEE_EN); + + String json = MAPPER.writeValueAsString(DeepgramModel.FLUX_RENEE_EN); + assertThat(json).isEqualTo("\"flux-renee-en\""); + assertThat(MAPPER.readValue(json, DeepgramModel.class)).isEqualTo(DeepgramModel.FLUX_RENEE_EN); + } + } + /** * Coverage for public-surface changes introduced by the 2026-08-11 regeneration. These are * breaking (documented in {@code docs/Migrating-v0.7-to-v0.8.md}); the tests pin the new typed From 7d96a56ff0d0fdecb452c604da84c1ffd4110e83 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 11:59:19 +0100 Subject: [PATCH 09/12] docs: list the seven carrier types affected by the provider renames An API surface diff against 0.8.0 (javap over all 1688 public types) showed the renames ripple further than the type list implies: 30 public members are removed from 21 surviving types, because seven carrier types changed both their getter return types and their builder parameter types. The guide previously made this point only for Google.getVersion(). Now lists all seven carriers with their affected members, and distinguishes the inline-constant case (import change is enough) from stored values (declarations need updating). The same diff confirmed the scope is bounded: all 53 removed public types and all 30 removed members trace to the 11 renamed/deleted types, with zero unexplained removals and no new required builder stages. --- docs/Migrating-v0.8-to-v0.9.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/Migrating-v0.8-to-v0.9.md b/docs/Migrating-v0.8-to-v0.9.md index ac0d535..99e5a4c 100644 --- a/docs/Migrating-v0.8-to-v0.9.md +++ b/docs/Migrating-v0.8-to-v0.9.md @@ -128,7 +128,19 @@ Google google = Google.builder() Optional version = google.getVersion(); ``` -Note that the getter return types change too, so a declared `Optional` must be updated — not just the builder call. +Note that the getter return types change too, so a declared `Optional` must be updated — not just the builder call. Seven carrier types are affected this way, on both their getters and their builder parameters: + +| Carrier | Affected members | +| --- | --- | +| `Anthropic` | `getModel()`, `model(...)` | +| `Cartesia` | `getModelId()`, `getVoice()`, `modelId(...)`, `voice(...)` | +| `Deepgram` | `getModel()`, `model(...)` | +| `Google` | `getModel()`, `getVersion()`, `model(...)`, `version(...)` | +| `Groq` | `getReasoningMode()`, `reasoningMode(...)` | +| `ConversationHistoryMessage` | `getRole()`, `role(...)` | +| `FunctionCallHistoryMessage` | `getFunctionCalls()`, `functionCalls(...)`, `addFunctionCalls(...)`, `addAllFunctionCalls(...)` | + +If you only pass constants inline — `Google.builder().model(GoogleModel.GEMINI25FLASH)` — updating the import is enough. If you store or return these values in your own fields, variables, or method signatures, those declarations need updating too. `OpenAiThinkProvider` and `OpenAiThinkProviderModel` are **not** renamed and keep their existing names. From 2f5923e2ec599c429d78f7b6ed9b1a7ce93e74fe Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 12:04:02 +0100 Subject: [PATCH 10/12] style: satisfy spotless after the rename pass The provider renames shortened type names enough that two builder chains in examples/agent/ProviderCombinations.java now fit on one line, so the formatter wanted them unwrapped. Applied ./gradlew spotlessApply -- cosmetic only. Also corrects the documented post-regen validation command in AGENTS.md to ./gradlew spotlessCheck test compileExamples. CI runs Spotless Check as its own job, so the previously documented 'test compileExamples' passed locally while CI failed, which is exactly what happened here. Re-applying patches by hand tends to trip it, since inserted comments and renamed types change line lengths and hence how chained calls wrap. --- AGENTS.md | 2 +- examples/agent/ProviderCombinations.java | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ece5aae..56bba3e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -86,5 +86,5 @@ The `.bak` files are our manually patched versions protected by `.fernignore`. T ``` Ignore `core/Nullable.java` and `core/NullableNonemptyFilter.java` (Jackson utility classes, not message types). Anything else in the list needs the `hashCode()` patch, a `.fernignore` entry, an entry in the list above, and a `RegenTypesTest.FieldsLessMessageContract` assertion. `ListenV2ForceEndTurn` reached `main` unpatched this way. -7. Run checks (`./gradlew test compileExamples`) to verify. `test` covers unit/wire tests including the README snippet compilation; `compileExamples` separately compiles the hand-maintained `examples/` directory and catches stale API call sites that `test` alone would miss. +7. Run checks (`./gradlew spotlessCheck test compileExamples`) to verify. `test` covers unit/wire tests including the README snippet compilation; `compileExamples` separately compiles the hand-maintained `examples/` directory and catches stale API call sites that `test` alone would miss. **`spotlessCheck` is required** — CI runs it as a separate job, so `test compileExamples` alone can pass locally while CI fails. Re-applying patches by hand almost always trips it: inserted comments and renamed types change line lengths, which changes how the formatter wraps chained calls. Run `./gradlew spotlessApply` to fix, then re-check. 8. Commit as `chore: re-apply manual patches after regen` and push. diff --git a/examples/agent/ProviderCombinations.java b/examples/agent/ProviderCombinations.java index 31b9af7..ee7c640 100644 --- a/examples/agent/ProviderCombinations.java +++ b/examples/agent/ProviderCombinations.java @@ -28,9 +28,8 @@ public static void main(String[] args) { System.out.println(); // Shared speak provider (Deepgram TTS) - Deepgram deepgramSpeak = Deepgram.builder() - .model(DeepgramModel.AURA2ASTERIA_EN) - .build(); + Deepgram deepgramSpeak = + Deepgram.builder().model(DeepgramModel.AURA2ASTERIA_EN).build(); AgentV1SettingsAgentContextSpeak speakSettings = AgentV1SettingsAgentContextSpeak.of(SpeakSettingsV1.builder() .provider(SpeakSettingsV1Provider.deepgram(deepgramSpeak)) .build()); @@ -56,9 +55,8 @@ public static void main(String[] args) { // Combination 2: Anthropic Claude + Deepgram System.out.println("=== Combination 2: Anthropic + Deepgram ==="); - Anthropic anthropicProvider = Anthropic.builder() - .model(AnthropicModel.CLAUDE_SONNET420250514) - .build(); + Anthropic anthropicProvider = + Anthropic.builder().model(AnthropicModel.CLAUDE_SONNET420250514).build(); AgentV1SettingsAgent anthropicConfig = AgentV1SettingsAgent.of(AgentV1SettingsAgentContext.builder() .think(AgentV1SettingsAgentContextThink.of(ThinkSettingsV1.builder() From 3ea4dcda5a6488d7ed3f131777b5da07f4deac33 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 19 Aug 2026 14:02:34 +0100 Subject: [PATCH 11/12] test: cover the untested surface from this regen Closes the coverage gaps found while auditing the branch. 113 -> 123 tests. ListenV2ControlFrameWireTest (new): sendForceEndTurn had no test at all. The server gates the message on every deployment we can reach, so an end-to-end test is impossible -- but that does not block asserting the half we own, that the client puts a correctly serialized frame on the wire. Three cases: the frame reaches the server, it carries nothing but the discriminator (asserted with an exact match, since the server rejects unknown fields on control messages), and sendCloseStream works as a control so a future failure distinguishes "the send path broke" from "ForceEndTurn broke". RegenTypesTest.Regen20260819 (new nested class): the read-side fields this regen added had no coverage, and being additive, a later regen could drop or rename one without any test noticing. Seven cases across ListenV2TurnInfo.trigger, both DiarizeInfo variants (listen.v1.types and types), and words-item speaker_confidence -- each asserted from a realistic server payload rather than a builder round-trip, so they exercise the server-to-SDK direction. Every field is also asserted in the absent case, which is what deployed clients hit today: trigger is not emitted until the force-end-turn gate lifts, diarize_info only appears when diarization is on, and speaker_confidence is pre-recorded only. IntegrationTest: the SpeakV2WebSocket test now sets speed(1.05). The speed patch was previously guarded only by a mock-server wire test, so nothing exercised it against a real server. Verified passing against the live API; a regression to the generator's string form would surface as a server error in that test. Verified: ./gradlew spotlessCheck test compileExamples -> 123 tests, 0 failures, 1 skipped (SpeakV2WebSocket, opt-in via DEEPGRAM_SPEAK_V2_WS). With that flag set, integrationTest is 7/7. --- .../java/com/deepgram/IntegrationTest.java | 7 ++ .../ListenV2ControlFrameWireTest.java | 116 ++++++++++++++++++ .../java/com/deepgram/RegenTypesTest.java | 116 ++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 src/test/java/com/deepgram/ListenV2ControlFrameWireTest.java diff --git a/src/test/java/com/deepgram/IntegrationTest.java b/src/test/java/com/deepgram/IntegrationTest.java index caefd1f..4e56faf 100644 --- a/src/test/java/com/deepgram/IntegrationTest.java +++ b/src/test/java/com/deepgram/IntegrationTest.java @@ -280,6 +280,13 @@ void testIntegration_SpeakV2WebSocket() throws Exception { .model("flux-alexis-en") .encoding(SpeakV2Encoding.LINEAR16) .sampleRate(SpeakV2SampleRate.SIXTEEN_THOUSAND) + // speed is sent as a number on the connect URL. The generator retyped it to a + // closed string enum in the 2026-08-19 regen and we patched it back to Double; + // setting it here exercises that patch against the live server, which the + // mock-server wire test in SpeakV2ConnectWireTest cannot do. An out-of-range or + // off-increment value comes back as SPEED_OUT_OF_RANGE / SPEED_INCREMENT_INVALID, + // so a regression to the string form would surface as a server error below. + .speed(1.05) .build(); wsClient.connect(options).get(15, TimeUnit.SECONDS); diff --git a/src/test/java/com/deepgram/ListenV2ControlFrameWireTest.java b/src/test/java/com/deepgram/ListenV2ControlFrameWireTest.java new file mode 100644 index 0000000..bf1b386 --- /dev/null +++ b/src/test/java/com/deepgram/ListenV2ControlFrameWireTest.java @@ -0,0 +1,116 @@ +package com.deepgram; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.deepgram.core.Environment; +import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; +import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; +import com.deepgram.resources.listen.v2.websocket.V2ConnectOptions; +import com.deepgram.resources.listen.v2.websocket.V2WebSocketClient; +import com.deepgram.types.ListenV2Model; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Wire coverage for the listen v2 client-to-server control frames, driven against MockWebServer. + * + *

{@code ForceEndTurn} is the reason this class exists. The message is currently rejected by the + * live API on every deployment we can reach — the server recognises the type but refuses it with + * {@code UNPARSABLE_CLIENT_MESSAGE} / "The ForceEndTurn message is not enabled on this deployment." + * That gate makes an end-to-end integration test impossible, but it does not stop us from asserting + * the half we own: that {@link V2WebSocketClient#sendForceEndTurn} puts a correctly serialized frame + * on the wire. Without this, a public SDK method would ship with nothing verifying it at all, and a + * future regen could reshape the frame silently. + * + *

{@code CloseStream} is included as a control: it exercises the same send path with a message the + * server does accept, so a failure here distinguishes "the send path broke" from "ForceEndTurn + * specifically broke". + * + *

Frozen via {@code src/test/} in .fernignore. + */ +class ListenV2ControlFrameWireTest { + private MockWebServer server; + private DeepgramClient client; + + @BeforeEach + void setUp() throws Exception { + server = new MockWebServer(); + server.start(); + String base = server.url("/").toString().replaceAll("/$", ""); + Environment env = Environment.custom() + .base(base) + .production(base) + .agent(base) + .agentRest(base) + .build(); + client = DeepgramClient.builder().apiKey("test").environment(env).build(); + } + + @AfterEach + void tearDown() throws Exception { + server.shutdown(); + } + + /** Connects, keeps the socket open, runs {@code action}, and returns the first frame the server received. */ + private String connectAndCaptureSentFrame(java.util.function.Consumer action) throws Exception { + BlockingQueue received = new LinkedBlockingQueue<>(); + server.enqueue(new MockResponse().withWebSocketUpgrade(new WebSocketListener() { + @Override + public void onMessage(WebSocket webSocket, String text) { + received.add(text); + // Close server-side once we've captured a frame so MockWebServer can shut down cleanly. + webSocket.close(1000, null); + } + })); + V2WebSocketClient ws = client.listen().v2().v2WebSocket(); + try { + ws.connect(V2ConnectOptions.builder() + .model(ListenV2Model.FLUX_GENERAL_EN) + .build()) + .get(5, TimeUnit.SECONDS); + action.accept(ws); + return received.poll(5, TimeUnit.SECONDS); + } finally { + ws.disconnect(); + } + } + + @Test + @DisplayName("sendForceEndTurn serializes a ForceEndTurn frame") + void sendForceEndTurnFrame() throws Exception { + String frame = + connectAndCaptureSentFrame(ws -> ws.sendForceEndTurn(ListenV2ForceEndTurn.builder().build())); + + assertThat(frame).as("ForceEndTurn frame reached the server").isNotNull(); + assertThat(frame).contains("\"type\":\"ForceEndTurn\""); + } + + @Test + @DisplayName("ForceEndTurn is a no-payload control message: type is its only field") + void forceEndTurnCarriesNoPayload() throws Exception { + String frame = + connectAndCaptureSentFrame(ws -> ws.sendForceEndTurn(ListenV2ForceEndTurn.builder().build())); + + // The server rejects unknown fields on control messages, so the frame must carry nothing but + // the discriminator. Asserted exactly rather than with contains() to catch stray fields. + assertThat(frame).isEqualTo("{\"type\":\"ForceEndTurn\"}"); + } + + @Test + @DisplayName("sendCloseStream serializes a CloseStream frame (control for the send path)") + void sendCloseStreamFrame() throws Exception { + String frame = connectAndCaptureSentFrame(ws -> ws.sendCloseStream(ListenV2CloseStream.builder().build())); + + assertThat(frame).as("CloseStream frame reached the server").isNotNull(); + assertThat(frame).contains("\"type\":\"CloseStream\""); + } +} diff --git a/src/test/java/com/deepgram/RegenTypesTest.java b/src/test/java/com/deepgram/RegenTypesTest.java index e3a4016..0b947c8 100644 --- a/src/test/java/com/deepgram/RegenTypesTest.java +++ b/src/test/java/com/deepgram/RegenTypesTest.java @@ -12,8 +12,11 @@ import com.deepgram.resources.agent.v1.types.AgentV1ThinkUpdated; import com.deepgram.resources.agent.v1.types.AgentV1UpdateListenListenProvider; import com.deepgram.resources.agent.v1.types.AgentV1UserStartedSpeaking; +import com.deepgram.resources.listen.v1.types.ListenV1ResultsMetadata; import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; +import com.deepgram.resources.listen.v2.types.ListenV2TurnInfo; +import com.deepgram.resources.listen.v2.types.ListenV2TurnInfoEvent; import com.deepgram.resources.listen.v2.types.ListenV2TurnInfoWordsItem; import com.deepgram.resources.speak.v2.types.SpeakV2Close; import com.deepgram.resources.speak.v2.types.SpeakV2Flush; @@ -22,6 +25,8 @@ import com.deepgram.types.Google; import com.deepgram.types.GoogleModel; import com.deepgram.types.GoogleVersion; +import com.deepgram.types.ListenV1ResponseMetadata; +import com.deepgram.types.ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem; import com.deepgram.types.ListenV2Redact; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; @@ -219,4 +224,115 @@ void listenV2RedactWireValue() { assertThat(ListenV2Redact.AGGRESSIVE_NUMBERS.toString()).isEqualTo("aggressive_numbers"); } } + + /** + * Coverage for the read-side fields added by the 2026-08-19 regeneration. These are additive, so + * nothing breaks if they are absent — which is exactly the risk: a regen could drop or rename one + * and no existing test would notice. Each is asserted from a realistic server payload (parsing the + * JSON rather than round-tripping a builder) so the tests exercise the direction that actually + * matters, server to SDK, including the absent case. + */ + @Nested + @DisplayName("2026-08-19 regen read-side fields") + class Regen20260819 { + + @Test + @DisplayName("ListenV2TurnInfo.trigger is surfaced when the server sends it") + void turnInfoTriggerPresent() throws Exception { + // trigger identifies what ended a turn. Typed as an open String rather than an enum, so a + // new server-side value cannot break a deployed client. + String json = "{\"type\":\"TurnInfo\",\"request_id\":\"req-1\",\"sequence_id\":3," + + "\"event\":\"EndOfTurn\",\"turn_index\":0,\"audio_window_start\":0.0," + + "\"audio_window_end\":1.5,\"transcript\":\"hello\",\"end_of_turn_confidence\":0.91," + + "\"trigger\":\"manual\"}"; + + ListenV2TurnInfo turn = MAPPER.readValue(json, ListenV2TurnInfo.class); + + assertThat(turn.getTrigger()).contains("manual"); + assertThat(turn.getEvent()).isEqualTo(ListenV2TurnInfoEvent.END_OF_TURN); + } + + @Test + @DisplayName("ListenV2TurnInfo.trigger is empty when the server omits it") + void turnInfoTriggerAbsent() throws Exception { + // The live API does not emit trigger yet, so the absent case is the one deployed clients + // actually hit today. It must parse cleanly rather than failing or defaulting to a value. + String json = "{\"type\":\"TurnInfo\",\"request_id\":\"req-1\",\"sequence_id\":1," + + "\"event\":\"Update\",\"turn_index\":0,\"audio_window_start\":0.0," + + "\"audio_window_end\":0.5,\"transcript\":\"\",\"end_of_turn_confidence\":0.1}"; + + ListenV2TurnInfo turn = MAPPER.readValue(json, ListenV2TurnInfo.class); + + assertThat(turn.getTrigger()).isEmpty(); + } + + @Test + @DisplayName("ListenV1 results metadata exposes diarize_info (model_uuid + arch)") + void resultsMetadataDiarizeInfo() throws Exception { + String json = "{\"request_id\":\"req-2\",\"model_uuid\":\"m-uuid\"," + + "\"model_info\":{\"name\":\"nova-3\",\"version\":\"1\",\"arch\":\"nova-3\"}," + + "\"diarize_info\":{\"model_uuid\":\"d-uuid\",\"arch\":\"v1\"}}"; + + ListenV1ResultsMetadata meta = MAPPER.readValue(json, ListenV1ResultsMetadata.class); + + assertThat(meta.getDiarizeInfo()).isPresent(); + assertThat(meta.getDiarizeInfo().get().getModelUuid()).isEqualTo("d-uuid"); + assertThat(meta.getDiarizeInfo().get().getArch()).isEqualTo("v1"); + } + + @Test + @DisplayName("ListenV1 results metadata diarize_info is empty when diarization is off") + void resultsMetadataDiarizeInfoAbsent() throws Exception { + String json = "{\"request_id\":\"req-2\",\"model_uuid\":\"m-uuid\"," + + "\"model_info\":{\"name\":\"nova-3\",\"version\":\"1\",\"arch\":\"nova-3\"}}"; + + ListenV1ResultsMetadata meta = MAPPER.readValue(json, ListenV1ResultsMetadata.class); + + assertThat(meta.getDiarizeInfo()).isEmpty(); + } + + @Test + @DisplayName("ListenV1 response metadata exposes its own diarize_info variant") + void responseMetadataDiarizeInfo() throws Exception { + // The pre-recorded response carries a parallel DiarizeInfo type under com.deepgram.types; + // both were added this regen, so both need a guard. + String json = "{\"request_id\":\"req-3\",\"created\":\"2026-08-19T00:00:00Z\"," + + "\"duration\":1.0,\"channels\":1,\"sha256\":\"abc\",\"transaction_key\":\"tk\"," + + "\"model_info\":{},\"diarize_info\":{\"model_uuid\":\"d-uuid\",\"arch\":\"v1\"}}"; + + ListenV1ResponseMetadata meta = MAPPER.readValue(json, ListenV1ResponseMetadata.class); + + assertThat(meta.getDiarizeInfo()).isPresent(); + assertThat(meta.getDiarizeInfo().get().getModelUuid()).isEqualTo("d-uuid"); + assertThat(meta.getDiarizeInfo().get().getArch()).isEqualTo("v1"); + } + + @Test + @DisplayName("pre-recorded words item exposes speaker_confidence alongside speaker") + void wordsItemSpeakerConfidence() throws Exception { + String json = "{\"word\":\"hello\",\"start\":0.1,\"end\":0.4,\"confidence\":0.99," + + "\"speaker\":0,\"speaker_confidence\":0.87}"; + + ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem word = + MAPPER.readValue(json, ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem.class); + + assertThat(word.getSpeaker()).contains(0); + assertThat(word.getSpeakerConfidence()).isPresent(); + assertThat(word.getSpeakerConfidence().get()).isEqualTo(0.87f); + } + + @Test + @DisplayName("words item speaker_confidence is empty when diarization is off") + void wordsItemSpeakerConfidenceAbsent() throws Exception { + // speaker_confidence is pre-recorded only -- it is never returned for streaming, so the + // empty case must stay clean rather than throwing on a streaming-shaped payload. + String json = "{\"word\":\"hello\",\"start\":0.1,\"end\":0.4,\"confidence\":0.99}"; + + ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem word = + MAPPER.readValue(json, ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem.class); + + assertThat(word.getSpeakerConfidence()).isEmpty(); + assertThat(word.getSpeaker()).isEmpty(); + } + } } From 6aa40c2d4cc7ca6146fca13fdb16f27c7a232c78 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Thu, 20 Aug 2026 13:25:45 +0100 Subject: [PATCH 12/12] test: cover listen v2 force-end-turn against the live API The ForceEndTurn gate has opened on staging, so the feature is now verifiable end-to-end rather than only at the wire level. Add an opt-in integration test (DEEPGRAM_LISTEN_V2_FORCE_END_TURN=1) that streams real audio, waits for StartOfTurn, forces the turn to end, and asserts trigger=manual with no server error. It skips cleanly where the feature is gated, matching the existing DEEPGRAM_SPEAK_V2_WS pattern. This is the first live coverage of the listen v2 WebSocket in this repo. Add examples/listen/ForceEndTurn.java, the first example that actually streams audio to listen v2 and so the first to reach a TurnInfo. It reports and exits cleanly on a deployment without the feature. Correct the claims that the gate was closed everywhere. The earlier evidence rested on trigger being absent from every TurnInfo, but trigger only appears on EndOfTurn, which a gated deployment never reaches once it closes the socket. Streaming through to a natural EndOfTurn is what actually shows the field is not emitted. --- docs/Migrating-v0.8-to-v0.9.md | 12 +- examples/listen/ForceEndTurn.java | 169 ++++++++++++++++++ .../java/com/deepgram/IntegrationTest.java | 124 +++++++++++++ .../ListenV2ControlFrameWireTest.java | 15 +- .../java/com/deepgram/RegenTypesTest.java | 8 +- 5 files changed, 314 insertions(+), 14 deletions(-) create mode 100644 examples/listen/ForceEndTurn.java diff --git a/docs/Migrating-v0.8-to-v0.9.md b/docs/Migrating-v0.8-to-v0.9.md index 99e5a4c..823aec2 100644 --- a/docs/Migrating-v0.8-to-v0.9.md +++ b/docs/Migrating-v0.8-to-v0.9.md @@ -212,11 +212,15 @@ V2WebSocketClient wsClient = client.listen().v2().v2WebSocket(); wsClient.sendForceEndTurn(ListenV2ForceEndTurn.builder().build()); ``` -**This requires server-side enablement and is not yet generally available.** The typed surface ships now so callers are ready, but do not depend on it until the feature is enabled for your deployment — sending it before then is rejected by the server. +The turn ends immediately regardless of end-of-turn confidence, and the connection stays open — the turn index advances and transcription continues into the next turn. The resulting `EndOfTurn` carries `trigger` set to `"manual"`. + +**This requires server-side enablement and is not yet enabled on all deployments.** Where it is not enabled the server rejects the message with `UNPARSABLE_CLIENT_MESSAGE` ("The ForceEndTurn message is not enabled on this deployment.") and closes the connection, so guard against that path until you have confirmed the feature is live for the deployment you target. ### Listen V2 Turn Trigger -`ListenV2TurnInfo` gains `getTrigger()`, returning `Optional`, which identifies what ended a turn (for example `model`, `manual`, or `timeout`). It is typed as an open `String` so new server-side trigger values do not break deployed clients. +`ListenV2TurnInfo` gains `getTrigger()`, returning `Optional`, which identifies what ended a turn: `model` for Flux's own end-of-turn detection, `manual` for a turn ended by `sendForceEndTurn`, and `timeout` when `eot_timeout_ms` elapsed. It is typed as an open `String` so new server-side trigger values do not break deployed clients. + +It is populated on `EndOfTurn` events and only there, so treat the empty case as normal — it is what every `Update` and `StartOfTurn` carries, and also what a deployment that does not yet emit `trigger` returns throughout. ### Listen V1 Diarization Detail @@ -262,8 +266,8 @@ Both were changed by the generator this cycle; the SDK patches them back, so no ### New Features -- `listen.v2` `sendForceEndTurn(ListenV2ForceEndTurn)` — requires server-side enablement, not yet generally available -- `ListenV2TurnInfo.getTrigger()` +- `listen.v2` `sendForceEndTurn(ListenV2ForceEndTurn)` — requires server-side enablement, not yet enabled on all deployments +- `ListenV2TurnInfo.getTrigger()` — present on `EndOfTurn` events, on deployments that emit it - `ListenV1ResponseMetadata.getDiarizeInfo()` / `ListenV1ResultsMetadata.getDiarizeInfo()` - `getSpeakerConfidence()` on pre-recorded words items - 25 new `DeepgramModel` constants diff --git a/examples/listen/ForceEndTurn.java b/examples/listen/ForceEndTurn.java new file mode 100644 index 0000000..9bc6c8b --- /dev/null +++ b/examples/listen/ForceEndTurn.java @@ -0,0 +1,169 @@ +import com.deepgram.DeepgramClient; +import com.deepgram.DeepgramClientBuilder; +import com.deepgram.core.Environment; +import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; +import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; +import com.deepgram.resources.listen.v2.types.ListenV2TurnInfoEvent; +import com.deepgram.resources.listen.v2.websocket.V2ConnectOptions; +import com.deepgram.resources.listen.v2.websocket.V2WebSocketClient; +import com.deepgram.types.ListenV2Encoding; +import com.deepgram.types.ListenV2Model; +import com.deepgram.types.ListenV2SampleRate; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import okio.ByteString; + +/** + * Ending a Listen V2 turn on demand with {@code ForceEndTurn}, and reading back which cause ended the turn via + * {@code TurnInfo.getTrigger()}. + * + *

Normally Flux decides when a turn is over. {@code ForceEndTurn} lets the application decide instead — useful when + * something outside the audio tells you the speaker is done, such as a push-to-talk button being released. The turn + * ends immediately regardless of end-of-turn confidence, and the connection stays open for the next turn. + * + *

{@code ForceEndTurn} is gated per deployment. Where it is not enabled the server replies + * {@code UNPARSABLE_CLIENT_MESSAGE} and closes the connection; this example reports that and exits rather than failing. + * Point it at an environment with the feature enabled via DEEPGRAM_BASE_URL. + * + *

Usage: java ForceEndTurn + */ +public class ForceEndTurn { + + private static final String AUDIO_URL = "https://dpgr.am/spacewalk.wav"; + + // spacewalk.wav is 16-bit mono PCM at 44.1kHz behind a standard 44-byte RIFF header. + private static final int SAMPLE_RATE = 44100; + private static final int WAV_HEADER_BYTES = 44; + private static final int CHUNK_BYTES = SAMPLE_RATE / 10 * 2; // 100ms of audio + + public static void main(String[] args) { + // Get API key from environment + String apiKey = System.getenv("DEEPGRAM_API_KEY"); + if (apiKey == null || apiKey.isEmpty()) { + System.err.println("DEEPGRAM_API_KEY environment variable is required"); + System.exit(1); + } + + System.out.println("Force-end-turn (Listen V2 WebSocket)"); + System.out.println(); + + DeepgramClientBuilder builder = DeepgramClient.builder().apiKey(apiKey); + + // ForceEndTurn is not enabled on every deployment. Set DEEPGRAM_BASE_URL (wss://...) to + // target one where it is. + String baseUrl = System.getenv("DEEPGRAM_BASE_URL"); + if (baseUrl != null && !baseUrl.isEmpty()) { + String https = baseUrl.startsWith("wss://") ? "https://" + baseUrl.substring("wss://".length()) : baseUrl; + builder.environment(Environment.custom() + .base(https) + .production(baseUrl) + .agent(baseUrl) + .agentRest(https) + .build()); + System.out.println("Targeting " + baseUrl); + } + + DeepgramClient client = builder.build(); + V2WebSocketClient wsClient = client.listen().v2().v2WebSocket(); + + CountDownLatch turnStarted = new CountDownLatch(1); + CountDownLatch endOfTurn = new CountDownLatch(1); + AtomicBoolean featureDisabled = new AtomicBoolean(false); + + try { + byte[] audio = download(AUDIO_URL); + + wsClient.onConnected(connected -> System.out.println("Connected: request_id=" + connected.getRequestId())); + + wsClient.onTurnInfo(turn -> { + if (turn.getEvent().equals(ListenV2TurnInfoEvent.START_OF_TURN)) { + turnStarted.countDown(); + System.out.println("[StartOfTurn] turn=" + turn.getTurnIndex()); + } else if (turn.getEvent().equals(ListenV2TurnInfoEvent.END_OF_TURN)) { + // trigger is present on EndOfTurn and only there. "manual" means a ForceEndTurn + // ended this turn; "model" means Flux's own detection did; "timeout" means + // eot_timeout_ms elapsed. It is an open enum, so tolerate unfamiliar values. + System.out.printf( + "[EndOfTurn] turn=%d trigger=%s end_of_turn_confidence=%.4f%n", + turn.getTurnIndex(), + turn.getTrigger().orElse(""), + turn.getEndOfTurnConfidence()); + System.out.println(" transcript: \"" + turn.getTranscript() + "\""); + endOfTurn.countDown(); + } + }); + + wsClient.onErrorMessage(error -> { + if ("UNPARSABLE_CLIENT_MESSAGE".equals(error.getCode())) { + featureDisabled.set(true); + } + System.err.println("Server error: " + error.getCode() + " - " + error.getDescription()); + endOfTurn.countDown(); + }); + + wsClient.onError(error -> System.err.println("Error: " + error.getMessage())); + + wsClient.connect(V2ConnectOptions.builder() + .model(ListenV2Model.FLUX_GENERAL_EN) + .encoding(ListenV2Encoding.LINEAR16) + .sampleRate(ListenV2SampleRate.of(SAMPLE_RATE)) + .build()) + .get(15, TimeUnit.SECONDS); + + // Stream audio until a turn is actually underway. Forcing an end before StartOfTurn + // would have no turn to end. + System.out.println("Streaming audio..."); + int offset = WAV_HEADER_BYTES; + for (int i = 0; i < 40 && offset < audio.length; i++) { + int len = Math.min(CHUNK_BYTES, audio.length - offset); + wsClient.sendMedia(ByteString.of(audio, offset, len)); + offset += len; + Thread.sleep(100); + if (turnStarted.getCount() == 0 && i >= 20) { + break; + } + } + + // Cut the turn off mid-sentence. Flux would have kept it open; we end it anyway. + System.out.println("Sending ForceEndTurn mid-sentence..."); + wsClient.sendForceEndTurn(ListenV2ForceEndTurn.builder().build()); + + endOfTurn.await(15, TimeUnit.SECONDS); + + if (featureDisabled.get()) { + System.out.println(); + System.out.println("ForceEndTurn is not enabled on this deployment."); + System.out.println("Set DEEPGRAM_BASE_URL to an environment where it is enabled."); + } else { + wsClient.sendCloseStream(ListenV2CloseStream.builder().build()); + } + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } finally { + wsClient.disconnect(); + } + } + + private static byte[] download(String url) throws Exception { + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("GET"); + try (InputStream in = conn.getInputStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buf = new byte[8192]; + int read; + while ((read = in.read(buf)) != -1) { + out.write(buf, 0, read); + } + return out.toByteArray(); + } finally { + conn.disconnect(); + } + } +} diff --git a/src/test/java/com/deepgram/IntegrationTest.java b/src/test/java/com/deepgram/IntegrationTest.java index 4e56faf..a6c2ef1 100644 --- a/src/test/java/com/deepgram/IntegrationTest.java +++ b/src/test/java/com/deepgram/IntegrationTest.java @@ -8,6 +8,10 @@ import com.deepgram.resources.listen.v1.media.requests.ListenV1RequestUrl; import com.deepgram.resources.listen.v1.media.requests.MediaTranscribeRequestOctetStream; import com.deepgram.resources.listen.v1.media.types.MediaTranscribeResponse; +import com.deepgram.resources.listen.v2.types.ListenV2CloseStream; +import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; +import com.deepgram.resources.listen.v2.types.ListenV2TurnInfo; +import com.deepgram.resources.listen.v2.types.ListenV2TurnInfoEvent; import com.deepgram.resources.read.v1.text.requests.TextAnalyzeRequest; import com.deepgram.resources.speak.v1.audio.requests.SpeakV1Request; import com.deepgram.resources.speak.v2.types.SpeakV2Close; @@ -21,6 +25,9 @@ import com.deepgram.types.ListenV1Response; import com.deepgram.types.ListenV1ResponseResults; import com.deepgram.types.ListenV1ResponseResultsChannelsItem; +import com.deepgram.types.ListenV2Encoding; +import com.deepgram.types.ListenV2Model; +import com.deepgram.types.ListenV2SampleRate; import com.deepgram.types.ReadV1Request; import com.deepgram.types.ReadV1RequestText; import com.deepgram.types.ReadV1Response; @@ -35,6 +42,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import okio.ByteString; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -51,6 +59,11 @@ public class IntegrationTest { private static final String TEST_AUDIO_URL = "https://dpgr.am/spacewalk.wav"; + /** spacewalk.wav is 16-bit mono PCM at 44.1kHz behind a standard 44-byte RIFF header. */ + private static final int AUDIO_SAMPLE_RATE = 44100; + + private static final int WAV_HEADER_BYTES = 44; + private DeepgramClient client; private String apiKey; @@ -316,5 +329,116 @@ void testIntegration_SpeakV2WebSocket() throws Exception { wsClient.disconnect(); } } + + @Test + @DisplayName("ListenV2ForceEndTurn - force a turn to end and read back trigger=manual") + void testIntegration_ListenV2ForceEndTurn() throws Exception { + // ForceEndTurn is gated per deployment: where it is not enabled the server rejects it + // with UNPARSABLE_CLIENT_MESSAGE / "The ForceEndTurn message is not enabled on this + // deployment." and closes the socket. So this test is opt-in: set + // DEEPGRAM_LISTEN_V2_FORCE_END_TURN=1, with DEEPGRAM_BASE_URL pointing at an environment + // that has the feature, if it is not yet enabled on the default host. Otherwise it is + // skipped rather than failing the build. + String enabled = System.getenv("DEEPGRAM_LISTEN_V2_FORCE_END_TURN"); + org.junit.jupiter.api.Assumptions.assumeTrue( + enabled != null && !enabled.isEmpty(), + "DEEPGRAM_LISTEN_V2_FORCE_END_TURN not set, skipping Listen v2 force-end-turn test"); + + byte[] wav = downloadAudio(); + + // Fully qualified: speak.v2's V2WebSocketClient / V2ConnectOptions are already imported + // above under those simple names, and Java has no import aliases. + com.deepgram.resources.listen.v2.websocket.V2WebSocketClient wsClient = + client.listen().v2().v2WebSocket(); + + CountDownLatch turnStarted = new CountDownLatch(1); + CountDownLatch endOfTurn = new CountDownLatch(1); + AtomicReference forcedTurn = new AtomicReference<>(); + AtomicReference serverError = new AtomicReference<>(); + + wsClient.onTurnInfo(turn -> { + if (turn.getEvent().equals(ListenV2TurnInfoEvent.START_OF_TURN)) { + turnStarted.countDown(); + } + if (turn.getEvent().equals(ListenV2TurnInfoEvent.END_OF_TURN) && forcedTurn.compareAndSet(null, turn)) { + endOfTurn.countDown(); + } + }); + wsClient.onErrorMessage(error -> serverError.set(error.getCode() + ": " + error.getDescription())); + wsClient.onError(error -> serverError.set(error.getMessage())); + + try { + wsClient.connect(com.deepgram.resources.listen.v2.websocket.V2ConnectOptions.builder() + .model(ListenV2Model.FLUX_GENERAL_EN) + .encoding(ListenV2Encoding.LINEAR16) + .sampleRate(ListenV2SampleRate.of(AUDIO_SAMPLE_RATE)) + .build()) + .get(15, TimeUnit.SECONDS); + + // Stream real audio until a turn is genuinely in progress, then force it to end. + // Forcing before StartOfTurn would prove nothing: there would be no open turn to end. + int offset = WAV_HEADER_BYTES; + int chunk = AUDIO_SAMPLE_RATE / 10 * 2; // 100ms of 16-bit mono + for (int i = 0; i < 40 && offset < wav.length; i++) { + int len = Math.min(chunk, wav.length - offset); + wsClient.sendMedia(ByteString.of(wav, offset, len)); + offset += len; + Thread.sleep(100); + if (turnStarted.getCount() == 0 && i >= 20) { + break; + } + } + assertThat(turnStarted.await(10, TimeUnit.SECONDS)) + .as("a turn started before we forced it to end") + .isTrue(); + + wsClient.sendForceEndTurn(ListenV2ForceEndTurn.builder().build()); + + assertThat(endOfTurn.await(15, TimeUnit.SECONDS)) + .as("received an EndOfTurn after ForceEndTurn") + .isTrue(); + + // The gate closing again would surface here first: the server rejects ForceEndTurn + // with UNPARSABLE_CLIENT_MESSAGE and closes the socket, so this assertion is what + // distinguishes "feature turned off" from "SDK send path broke". + assertThat(serverError.get()) + .as("no server/transport error after ForceEndTurn") + .isNull(); + + ListenV2TurnInfo turn = forcedTurn.get(); + assertThat(turn).as("captured the EndOfTurn").isNotNull(); + assertThat(turn.getTrigger()) + .as("trigger identifies the turn as manually ended") + .contains("manual"); + // A model-detected end would carry a high end_of_turn_confidence. A forced end + // arrives regardless of confidence, so a low value here is the evidence that the + // turn ended because we asked, not because Flux decided it was over. + System.out.println( + "ForceEndTurn -> trigger=" + turn.getTrigger().orElse("") + + " end_of_turn_confidence=" + turn.getEndOfTurnConfidence() + + " transcript=\"" + turn.getTranscript() + "\""); + + wsClient.sendCloseStream(ListenV2CloseStream.builder().build()); + } finally { + wsClient.disconnect(); + } + } + + private byte[] downloadAudio() throws Exception { + URL url = new URL(TEST_AUDIO_URL); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + try (InputStream in = conn.getInputStream(); + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream()) { + byte[] buf = new byte[8192]; + int read; + while ((read = in.read(buf)) != -1) { + out.write(buf, 0, read); + } + return out.toByteArray(); + } finally { + conn.disconnect(); + } + } } } diff --git a/src/test/java/com/deepgram/ListenV2ControlFrameWireTest.java b/src/test/java/com/deepgram/ListenV2ControlFrameWireTest.java index bf1b386..3c77fab 100644 --- a/src/test/java/com/deepgram/ListenV2ControlFrameWireTest.java +++ b/src/test/java/com/deepgram/ListenV2ControlFrameWireTest.java @@ -23,13 +23,14 @@ /** * Wire coverage for the listen v2 client-to-server control frames, driven against MockWebServer. * - *

{@code ForceEndTurn} is the reason this class exists. The message is currently rejected by the - * live API on every deployment we can reach — the server recognises the type but refuses it with - * {@code UNPARSABLE_CLIENT_MESSAGE} / "The ForceEndTurn message is not enabled on this deployment." - * That gate makes an end-to-end integration test impossible, but it does not stop us from asserting - * the half we own: that {@link V2WebSocketClient#sendForceEndTurn} puts a correctly serialized frame - * on the wire. Without this, a public SDK method would ship with nothing verifying it at all, and a - * future regen could reshape the frame silently. + *

{@code ForceEndTurn} is the reason this class exists. The message is gated per deployment — a + * deployment without it refuses the frame with {@code UNPARSABLE_CLIENT_MESSAGE} / "The ForceEndTurn + * message is not enabled on this deployment." {@code IntegrationTest} covers the end-to-end path + * where the feature is enabled, but that test is opt-in and skips wherever the gate is closed, so on + * a default run this class is the only thing asserting the half we own: that + * {@link V2WebSocketClient#sendForceEndTurn} puts a correctly serialized frame on the wire. Without + * it, a public SDK method could ship with nothing verifying it, and a future regen could reshape the + * frame silently. * *

{@code CloseStream} is included as a control: it exercises the same send path with a message the * server does accept, so a failure here distinguishes "the send path broke" from "ForceEndTurn diff --git a/src/test/java/com/deepgram/RegenTypesTest.java b/src/test/java/com/deepgram/RegenTypesTest.java index 0b947c8..ac69e53 100644 --- a/src/test/java/com/deepgram/RegenTypesTest.java +++ b/src/test/java/com/deepgram/RegenTypesTest.java @@ -240,7 +240,8 @@ class Regen20260819 { @DisplayName("ListenV2TurnInfo.trigger is surfaced when the server sends it") void turnInfoTriggerPresent() throws Exception { // trigger identifies what ended a turn. Typed as an open String rather than an enum, so a - // new server-side value cannot break a deployed client. + // new server-side value cannot break a deployed client. "manual" is the value a + // ForceEndTurn produces; this fixture mirrors the live EndOfTurn frame exactly. String json = "{\"type\":\"TurnInfo\",\"request_id\":\"req-1\",\"sequence_id\":3," + "\"event\":\"EndOfTurn\",\"turn_index\":0,\"audio_window_start\":0.0," + "\"audio_window_end\":1.5,\"transcript\":\"hello\",\"end_of_turn_confidence\":0.91," @@ -255,8 +256,9 @@ void turnInfoTriggerPresent() throws Exception { @Test @DisplayName("ListenV2TurnInfo.trigger is empty when the server omits it") void turnInfoTriggerAbsent() throws Exception { - // The live API does not emit trigger yet, so the absent case is the one deployed clients - // actually hit today. It must parse cleanly rather than failing or defaulting to a value. + // trigger is documented as present on EndOfTurn and only there, and a deployment without + // the feature omits it entirely. Either way the absent case must parse cleanly rather + // than failing or defaulting to a value. String json = "{\"type\":\"TurnInfo\",\"request_id\":\"req-1\",\"sequence_id\":1," + "\"event\":\"Update\",\"turn_index\":0,\"audio_window_start\":0.0," + "\"audio_window_end\":0.5,\"transcript\":\"\",\"end_of_turn_confidence\":0.1}";