diff --git a/.fern/metadata.json b/.fern/metadata.json index b0471248..87ba6bdd 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "5.95.1", "generatorName": "fernapi/fern-java-sdk", - "generatorVersion": "4.21.0", + "generatorVersion": "4.23.1", "generatorConfig": { "package-prefix": "com.deepgram", "base-api-exception-class-name": "DeepgramHttpException", @@ -12,8 +12,8 @@ "enable-wire-tests": true, "runtime-version": true }, - "originGitCommit": "f6f5d3876c8996e2a6b6e59136bb222183a8e317", + "originGitCommit": "e252995bcdb25f3e12d46ae342a2b93d0c1085f9", "originGitCommitIsDirty": true, "invokedBy": "manual", - "sdkVersion": "0.10.2" -} + "sdkVersion": "0.10.3" +} \ No newline at end of file diff --git a/.fernignore b/.fernignore index ee108186..ef6a8355 100644 --- a/.fernignore +++ b/.fernignore @@ -14,7 +14,8 @@ src/main/java/com/deepgram/AsyncDeepgramClientBuilder.java # Contains User-Agent, X-Fern-SDK-Name, and X-Fern-SDK-Version headers # with // x-release-please-version comments for automated version bumps. # Fern regen overwrites these with incorrect SDK names and strips the markers. -# Fern 4.21.0 owns the OkHttp lifecycle behavior; retain it while reapplying only these headers. +# Fern 4.22.1 closes tracked WebSockets before SDK-owned OkHttp resources; retain that lifecycle behavior while +# reapplying only these headers. src/main/java/com/deepgram/core/ClientOptions.java # Transport abstraction (pluggable transport for SageMaker, etc.) @@ -51,11 +52,6 @@ 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 -# Close is safe before connect in every generated WebSocket client. The listener is created only -# during connect(), so disconnect() must tolerate a null listener for try-with-resources clients. -# Remove these patches once Fern emits the null guard. -src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java - # 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 diff --git a/AGENTS.md b/AGENTS.md index 7c2f867e..bcef6fd2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,11 +47,10 @@ How to identify: Current temporarily frozen files: -- `src/main/java/com/deepgram/core/ClientOptions.java` - preserves release-please version markers and correct SDK header constants that Fern currently overwrites; use the standard `.bak` swap/restore workflow during regen review. Since generator 4.18.0 Fern emits a `getSdkVersion()` helper reading `Package.getImplementationVersion()` instead of a literal. That *does* resolve in the published artifact (CI publishes via `mvn deploy -P release`, and `pom.xml`'s maven-jar-plugin sets `addDefaultImplementationEntries=true`, so the JAR manifest carries `Implementation-Version`), but it resolves to `null` under Gradle and in tests, where it silently falls back to a hardcoded literal the generator does not keep current. We keep the explicit literals because they are correct in every context and because `.github/release-please-config.json` already lists this file in `extra-files`, so release-please bumps it alongside `pom.xml`, `build.gradle`, and `.fern/metadata.json`. Fern also emits `User-Agent` with a `com.deepgram.` prefix while leaving `X-Fern-SDK-Name` on the `com.deepgram:` Maven-coordinate form; we keep both on the colon form. Fern 4.21.0 generates `AutoCloseable` root clients and ownership-aware OkHttp cleanup; the permanent custom client wrappers inherit it, so do not add wrapper-local lifecycle code and retain Fern's lifecycle implementation during reconciliation. +- `src/main/java/com/deepgram/core/ClientOptions.java` - preserves release-please version markers and correct SDK header constants that Fern currently overwrites; use the standard `.bak` swap/restore workflow during regen review. Since generator 4.18.0 Fern emits a `getSdkVersion()` helper reading `Package.getImplementationVersion()` instead of a literal. That *does* resolve in the published artifact (CI publishes via `mvn deploy -P release`, and `pom.xml`'s maven-jar-plugin sets `addDefaultImplementationEntries=true`, so the JAR manifest carries `Implementation-Version`), but it resolves to `null` under Gradle and in tests, where it silently falls back to a hardcoded literal the generator does not keep current. We keep the explicit literals because they are correct in every context and because `.github/release-please-config.json` already lists this file in `extra-files`, so release-please bumps it alongside `pom.xml`, `build.gradle`, and `.fern/metadata.json`. Fern also emits `User-Agent` with a `com.deepgram.` prefix while leaving `X-Fern-SDK-Name` on the `com.deepgram:` Maven-coordinate form; we keep both on the colon form. Fern 4.22.1 tracks and closes child WebSockets before shutting down SDK-owned OkHttp resources; the permanent custom client wrappers inherit it, so do not add wrapper-local lifecycle code and retain Fern's lifecycle implementation during reconciliation. - `src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java` - carries bug fixes for `maxRetries(0)` semantics ("connect once, don't retry") and a configurable `connectionTimeoutMs` field (was hardcoded 4000ms), plus an `applyOptionsOverride(...)` hook used by `TransportWebSocketFactory` to apply per-transport reconnect policy; pull this back out once the fixes are upstreamed into the Fern generator. Use the standard `.bak` swap/restore workflow during regen review. - `src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java` and `src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java` - forward-compat patch (both clients). Fern's generated `handleIncomingMessage` dispatcher routes any unrecognized message type to `onError` with "Update your SDK version...", which makes a benign new server control frame look fatal to a deployed client. Patched so the unrecognized-type branch is a no-op — the raw frame is already delivered via `onMessage(String)` earlier in the method, so consumers still see it. Mirrors the JS/Python SDKs' forward-compat behavior and is regression-guarded by `src/test/java/com/deepgram/SpeakV2ForwardCompatTest.java` and `src/test/java/com/deepgram/ListenV2ForwardCompatTest.java`. These two clients also carry the streaming query-param patches described in the next entry. Use the standard `.bak` swap/restore workflow during regen review; re-apply the no-op to both after regen, and unfreeze once the generator stops treating unknown frames as errors. - `src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java` and `src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java` (and the v2 clients above) - streaming query-param patches on the generated `connect()` builders. Two fixes: (1) multi-value serialization — array-valued params (listen: `keyterm`, `keywords`, `replace`, `search`, `tag`, `extra`, `language_hint`; speak: `tag`) were serialized with `String.valueOf(union.get())`, collapsing a `List` into one param (`keyterm=[a, b]`) instead of repeats (`keyterm=a&keyterm=b`); (2) an `additionalProperties` escape hatch — the builder exposes `additionalProperty(key, value)` for unmodeled params (e.g. `no_delay`) but `connect()` never emitted them to the URL. Both patched to route through `QueryStringMapper(arraysAsRepeats=true)`, matching the REST path. Use the standard `.bak` swap/restore workflow during regen review; re-apply after regen and unfreeze once the generator emits array params as repeats and serializes `additionalProperties` on the WS `connect()` path (tracked as an upstream Fern request). -- All five generated WebSocket clients, including `src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java`, carry a `disconnect()` null guard so `close()` is safe before `connect()`. This keeps `AutoCloseable` safe for try-with-resources; re-apply the guards and retain the `.fernignore` entry until Fern emits them. - Fields-less message types carrying a manual `hashCode()` patch (Fern generates `equals()` but no `hashCode()` for these, violating the Object contract): `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java`, `src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java`, `src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java`, and the `AgentV1*` event types `src/main/java/com/deepgram/resources/agent/v1/types/{AgentV1ListenUpdated,AgentV1SpeakUpdated,AgentV1AgentAudioDone,AgentV1SettingsApplied,AgentV1UserStartedSpeaking,AgentV1KeepAlive,AgentV1ThinkUpdated,AgentV1PromptUpdated,AgentV1ForceEndTurn}.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze all of them once the generator emits a matching equals/hashCode pair for fields-less types (tracked as an upstream Fern request). - `src/main/java/com/deepgram/types/DeepgramModel.java` - restores the `FLUX_RENEE_EN` constant that generator 4.18.0 dropped. The voice is live: `POST /v2/speak?model=flux-renee-en` returns 200 with valid audio, and the name resolves in the server's model registry (an invented `flux-*` name is rejected with `INVALID_QUERY_PARAMETER`), so the removal is a spec regression rather than a retirement, and dropping the constant would break 0.8.0 callers for nothing. Five touchpoints: the constant, the `Value` enum entry, the `visit()` case, the `valueOf()` case, and the `Visitor` method. **This file is unlike the other temporarily frozen ones — it receives frequent additive spec changes (4.18.0 alone added 25 constants), so on the next regen do NOT restore the `.bak` wholesale.** Diff the `.bak` against the newly generated file, carry forward every new voice, and re-apply only the `FLUX_RENEE_EN` touchpoints. Drop the patch and unfreeze once the spec lists the voice again (tracked as an upstream spec request). - Union default-variant fix on the agent listen-provider unions: `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java`, `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java`. `version` is an optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits), but Fern points `@JsonTypeInfo` `defaultImpl` at the empty-bodied `_UnknownValue`, so such a payload deserializes to an unknown variant carrying `null` — `getProvider()` returns `null` and re-serialization emits `{"provider":null}`, silently dropping the provider on the wire. Patched to `defaultImpl = V2Value` on each; guarded by `src/test/java/com/deepgram/AgentSettingsProviderDefaultTest.java`. Use the standard `.bak` swap/restore workflow during regen review; drop the patches and unfreeze once the generator stops defaulting unions to the empty `_UnknownValue` (tracked as an upstream Fern request). diff --git a/README.md b/README.md index b116cf41..94cab35e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ You can learn more about the Deepgram API at [developers.deepgram.com](https://d ### Migrating Between Versions -- [v0.9 to v0.10](./docs/Migrating-v0.9-to-v0.10.md) (current) +- [v0.10 to v0.11](./docs/Migrating-v0.10-to-v0.11.md) (current) +- [v0.9 to v0.10](./docs/Migrating-v0.9-to-v0.10.md) - [v0.8 to v0.9](./docs/Migrating-v0.8-to-v0.9.md) - [v0.7 to v0.8](./docs/Migrating-v0.7-to-v0.8.md) - [v0.6 to v0.7](./docs/Migrating-v0.6-to-v0.7.md) @@ -70,9 +71,9 @@ Get your API key from the [Deepgram Console](https://console.deepgram.com/). ### Resource Lifecycle -Close an SDK-created client when your application is finished with it. This releases the OkHttp dispatcher and -connection pool. Close each WebSocket client before closing its root client; root-client cleanup does not close active -WebSocket clients for you. +Close an SDK-created client when your application is finished with it. This disconnects any WebSocket clients still +connected through it, then releases the OkHttp dispatcher and connection pool. Close an individual WebSocket client +earlier when its work is complete; after the root client closes, new WebSocket connections fail. ```java import com.deepgram.DeepgramClient; diff --git a/docs/Migrating-v0.10-to-v0.11.md b/docs/Migrating-v0.10-to-v0.11.md new file mode 100644 index 00000000..7c9287ec --- /dev/null +++ b/docs/Migrating-v0.10-to-v0.11.md @@ -0,0 +1,93 @@ +# v0.10 to v0.11 Migration Guide + +This guide covers the breaking source changes in Deepgram Java SDK `0.11.0`. The release corrects the shared Topics and Intents response shape, adds a Speak V2 Configure failure code, and includes Fern retry behavior updates. It also adds Flux TTS Controls support. + +## Update the dependency + +Upgrade to `0.11.0` with Gradle or Maven. + +**Gradle** + +```groovy +dependencies { + implementation 'com.deepgram:deepgram-java-sdk:0.11.0' +} +``` + +**Maven** + +```xml + + com.deepgram + deepgram-java-sdk + 0.11.0 + +``` + +## Topics and Intents segments + +`SharedTopics` and `SharedIntents` now expose their segments directly. The previous generated models reflected an extra `results.topics` or `results.intents` wrapper that is not present in the API payload. + +| Old path | New path | +| --- | --- | +| `SharedTopics.getResults().get().getTopics().get().getSegments()` | `SharedTopics.getSegments()` | +| `SharedIntents.getResults().get().getIntents().get().getSegments()` | `SharedIntents.getSegments()` | +| `SharedTopicsResults` | Removed; access segments through `SharedTopics.getSegments()` | +| `SharedTopicsResultsTopics` | Removed; access segments through `SharedTopics.getSegments()` | +| `com.deepgram.types.SharedTopicsResultsTopicsSegmentsItem` | `com.deepgram.types.SharedTopicsSegmentsItem` | +| `com.deepgram.types.SharedTopicsResultsTopicsSegmentsItemTopicsItem` | `com.deepgram.types.SharedTopicsSegmentsItemTopicsItem` | +| `SharedIntentsResults` | Removed; access segments through `SharedIntents.getSegments()` | +| `SharedIntentsResultsIntents` | Removed; access segments through `SharedIntents.getSegments()` | +| `com.deepgram.types.SharedIntentsResultsIntentsSegmentsItem` | `com.deepgram.types.SharedIntentsSegmentsItem` | +| `com.deepgram.types.SharedIntentsResultsIntentsSegmentsItemIntentsItem` | `com.deepgram.types.SharedIntentsSegmentsItemIntentsItem` | + +For example, replace the old Topics traversal: + +```java +List segments = topics.getResults().get() + .getTopics().get() + .getSegments().get(); +``` + +with: + +```java +List segments = topics.getSegments().get(); +``` + +Make the equivalent replacement for `SharedIntents` and its segment item type. + +## Speak V2 Configure failure visitor + +`SpeakV2ConfigureFailureCode` now includes `CONTROL_COMBINATION_INVALID`. Implementations of `SpeakV2ConfigureFailureCode.Visitor` must add `visitControlCombinationInvalid()`. + +```java +SpeakV2ConfigureFailureCode.Visitor visitor = new SpeakV2ConfigureFailureCode.Visitor<>() { + @Override + public String visitControlCombinationInvalid() { + return "Change the conflicting Controls configuration."; + } + + // Implement the remaining visitor methods. +}; +``` + +## Retry behavior + +Fern `4.21.2` and `4.22.2` change retry behavior for the SDK-created OkHttpClient without changing the public client-builder API. + +- The configured HTTP timeout is now applied to each attempt, rather than to the full retry loop. A request can therefore take longer than its configured timeout when retries and backoff occur. +- Before retrying a retryable HTTP response, the SDK buffers that response. If a later retry fails with a transport error, the SDK returns the earlier HTTP response instead of discarding its status, headers, and body. + +The tradeoffs are intentional: requests can occupy a caller for longer, and buffering holds a retryable response body in memory, but callers retain the actionable API error that triggered the retry instead of only observing a later connection failure. Set explicit timeouts and retry limits appropriate for latency-sensitive or large-response workloads, and continue to inspect returned HTTP errors during incident handling. Clients built with `.httpClient(...)` retain the caller-supplied retry policy, but the SDK still adds its logging and decompression interceptors. Setting `.timeout(...)` also overrides that client's call timeout. + +## Flux TTS Controls + +Flux TTS now supports inline Controls for speed, pauses, and pronunciation overrides. Controls are additive: batch requests use `SpeakV2Request.speed(...)` for speed and `SpeakV2Request.text(...)` for pause and pronunciation markers. Streaming requests use `V2ConnectOptions.speed(...)` or `SpeakV2Configure.speed(...)` for speed and `SpeakV2Speak.text(...)` for pronunciation controls; a pause marker on a streaming request fails with `DATA-0002`. + +```java +String pause = "\\{pause:500ms\\}"; +String pronunciation = "\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}"; +``` + +Pronunciation controls are Early Access. On batch requests, pronunciation cannot be combined with a non-default speed or with a pause; the API returns HTTP 400 with `CONTROL_COMBINATION_INVALID`. On streaming requests, a pause marker or a pronunciation control with a non-default speed emits `DATA-0002` and closes the WebSocket. A mid-stream `Configure` that changes speed while a buffered turn contains a pronunciation control returns `ConfigureFailure` with `CONTROL_COMBINATION_INVALID`. The Flux TTS Controls guide publishes with the Controls release; consult that guide for supported syntax and availability once it is live. diff --git a/docs/Migrating-v0.7-to-v0.8.md b/docs/Migrating-v0.7-to-v0.8.md index da7c8e29..55ebdcd5 100644 --- a/docs/Migrating-v0.7-to-v0.8.md +++ b/docs/Migrating-v0.7-to-v0.8.md @@ -8,7 +8,7 @@ The three breaking changes are: 1. **`AgentV1UpdateListenListen.getProvider()` retyped** from `DeepgramListenProviderV2` to the new `AgentV1UpdateListenListenProvider` V1/V2 union — the API now models the `provider` field as a versioned discriminated union. 2. **`Google.getVersion()` retyped** from `Optional` to `Optional` — the Google think-provider `version` field is now an enum. -3. **`SpeakV2SpeechMetadataControlsApplied` gained a required `breaksApplied` field** — the builder chain now includes a `breaksApplied(int)` step between `pronunciationsApplied(...)` and `pronunciationWarnings(...)`. The server always sends `0` at launch, because inline pause controls are not yet applied. +3. **`SpeakV2SpeechMetadataControlsApplied` gained a required `breaksApplied` field** — the builder chain now includes a `breaksApplied(int)` step between `pronunciationsApplied(...)` and `pronunciationWarnings(...)`. At the v0.8 launch, the server sent `0`; current Flux TTS batch and WebSocket behavior is documented in the [v0.10 to v0.11 migration guide](./Migrating-v0.10-to-v0.11.md#flux-tts-controls). ## Table of Contents @@ -164,7 +164,7 @@ Optional version = google.getVersion(); `SpeakV2SpeechMetadataControlsApplied` gained a required `breaksApplied` (`int`) field, reflecting a new `breaks_applied` field in the server payload. `SpeakV2SpeechMetadataControlsApplied` is a **server-emitted (read-only)** message, so most applications only read it — a new `getBreaksApplied()` getter is now available and no migration is needed for read paths. -> **Inline pause controls are not applied at launch** — support is coming soon. The server sends `breaks_applied` on every turn, but the value is always `0` until pause controls ship. The same is true of `pronunciationsApplied` and `pronunciationWarnings`. Read the field if you like, but do not branch on a non-zero value yet. +> **At the v0.8.0 launch**, inline pause controls were not applied and `breaks_applied` was always `0`. That historical limitation no longer applies to Flux TTS batch requests; see the [v0.10 to v0.11 migration guide](./Migrating-v0.10-to-v0.11.md#flux-tts-controls) for current Controls behavior and streaming restrictions. If you construct this type directly (uncommon — e.g. in tests), the staged builder now requires a `breaksApplied(int)` step between `pronunciationsApplied(...)` and `pronunciationWarnings(...)`. @@ -182,7 +182,7 @@ SpeakV2SpeechMetadataControlsApplied.builder() ```java SpeakV2SpeechMetadataControlsApplied.builder() .pronunciationsApplied(2) - .breaksApplied(0) // always 0 at launch — pause controls are not yet applied + .breaksApplied(0) // v0.8 launch behavior; Controls behavior has since changed .pronunciationWarnings(0) .build(); @@ -206,7 +206,7 @@ int breaks = controlsApplied.getBreaksApplied(); 1. **Agent update-listen provider union**: `AgentV1UpdateListenListen.getProvider()` / `provider(...)` now use `AgentV1UpdateListenListenProvider` (V1/V2 union) instead of `DeepgramListenProviderV2`. 2. **Google think-provider version enum**: `Google.getVersion()` / `version(...)` now use `GoogleThinkProviderVersion` instead of `String`. -3. **Speak V2 controls-applied field**: `SpeakV2SpeechMetadataControlsApplied` adds a required `breaksApplied` field (new builder step; new `getBreaksApplied()` getter). Always `0` at launch — inline pause controls are not yet applied. +3. **Speak V2 controls-applied field**: `SpeakV2SpeechMetadataControlsApplied` adds a required `breaksApplied` field (new builder step; new `getBreaksApplied()` getter). At the v0.8 launch the value was `0`; see the [v0.10 to v0.11 migration guide](./Migrating-v0.10-to-v0.11.md#flux-tts-controls) for current Controls behavior. ### Changed Signatures @@ -227,7 +227,7 @@ int breaks = controlsApplied.getBreaksApplied(); - [ ] Upgrade to `com.deepgram:deepgram-java-sdk:0.8.0` - [ ] Wrap `AgentV1UpdateListenListen` providers in `AgentV1UpdateListenListenProvider.v2(...)` (or `.v1(...)`) and read them via `getV2()` / `getV1()` - [ ] Replace `Google` `version` string literals with `GoogleThinkProviderVersion` constants and update any `Optional getVersion()` reads -- [ ] Add a `breaksApplied(...)` step to any hand-built `SpeakV2SpeechMetadataControlsApplied` — pass `0`, since inline pause controls are not applied at launch (read paths need no change) +- [ ] Add a `breaksApplied(...)` step to any hand-built `SpeakV2SpeechMetadataControlsApplied` — use `0` when reproducing the v0.8 launch payload shape (read paths need no change) - [ ] Rebuild your project and fix any remaining references to the changed signatures - [ ] (Optional) Adopt Speak V2 interrupt/configure, Listen V2 `redact`, client retry tuning, and the new Flux voices -``` \ No newline at end of file +``` diff --git a/examples/speak/FluxTtsControls.java b/examples/speak/FluxTtsControls.java new file mode 100644 index 00000000..333c9805 --- /dev/null +++ b/examples/speak/FluxTtsControls.java @@ -0,0 +1,59 @@ +import com.deepgram.DeepgramClient; +import com.deepgram.resources.speak.v2.audio.requests.SpeakV2Request; +import com.deepgram.resources.speak.v2.audio.types.AudioGenerateRequestEncoding; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +/** + * Generates Flux TTS batch audio with pause and pronunciation controls. + * + *

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

Pause and pronunciation controls cannot be combined in the same Flux TTS request. This example sends them + * separately. + */ +public class FluxTtsControls { + private static final String MODEL = "flux-alexis-en"; + + public static void main(String[] args) { + String apiKey = System.getenv("DEEPGRAM_API_KEY"); + if (apiKey == null || apiKey.isEmpty()) { + System.err.println("DEEPGRAM_API_KEY environment variable is required"); + System.exit(1); + } + + Path outputDirectory = args.length == 1 ? Path.of(args[0]) : Path.of("."); + + try (DeepgramClient client = DeepgramClient.builder().apiKey(apiKey).build()) { + Files.createDirectories(outputDirectory); + + generate( + client, + "Your confirmation number is four seven two. \\{pause:500ms\\} Is there anything else I can help with?", + outputDirectory.resolve("flux-pause.mp3")); + generate( + client, + "Take \\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\} twice daily.", + outputDirectory.resolve("flux-pronunciation.mp3")); + } catch (Exception e) { + System.err.println("Error generating Flux TTS Controls audio: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + private static void generate(DeepgramClient client, String text, Path outputPath) throws Exception { + SpeakV2Request request = SpeakV2Request.builder() + .model(MODEL) + .text(text) + .encoding(AudioGenerateRequestEncoding.MP3) + .build(); + + try (InputStream audio = client.speak().v2().audio().generate(request)) { + long bytes = Files.copy(audio, outputPath, StandardCopyOption.REPLACE_EXISTING); + System.out.printf("Audio saved to %s (%d bytes)%n", outputPath, bytes); + } + } +} diff --git a/src/main/java/com/deepgram/AsyncDeepgramApiClient.java b/src/main/java/com/deepgram/AsyncDeepgramApiClient.java index c2aa87ee..0ae6c114 100644 --- a/src/main/java/com/deepgram/AsyncDeepgramApiClient.java +++ b/src/main/java/com/deepgram/AsyncDeepgramApiClient.java @@ -79,8 +79,9 @@ public AsyncVoiceAgentClient voiceAgent() { } /** - * Releases resources owned by this client. See {@code ClientOptions.close()} for what is - * and is not released. + * Releases resources owned by this client: any WebSocket clients still connected through + * it are disconnected first, then the SDK-owned HTTP client is shut down. See + * {@code ClientOptions.close()} for what is and is not released. */ @Override public void close() { diff --git a/src/main/java/com/deepgram/DeepgramApiClient.java b/src/main/java/com/deepgram/DeepgramApiClient.java index ccb0d4a7..a67528ff 100644 --- a/src/main/java/com/deepgram/DeepgramApiClient.java +++ b/src/main/java/com/deepgram/DeepgramApiClient.java @@ -79,8 +79,9 @@ public VoiceAgentClient voiceAgent() { } /** - * Releases resources owned by this client. See {@code ClientOptions.close()} for what is - * and is not released. + * Releases resources owned by this client: any WebSocket clients still connected through + * it are disconnected first, then the SDK-owned HTTP client is shut down. See + * {@code ClientOptions.close()} for what is and is not released. */ @Override public void close() { diff --git a/src/main/java/com/deepgram/core/ClientOptions.java b/src/main/java/com/deepgram/core/ClientOptions.java index 5329810c..828cc2b5 100644 --- a/src/main/java/com/deepgram/core/ClientOptions.java +++ b/src/main/java/com/deepgram/core/ClientOptions.java @@ -3,10 +3,14 @@ */ package com.deepgram.core; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import okhttp3.OkHttpClient; @@ -21,6 +25,10 @@ public final class ClientOptions { private final boolean ownsHttpClient; + private final AtomicBoolean closed; + + private final Set openWebSockets; + private final int timeout; private final int maxRetries; @@ -41,6 +49,8 @@ private ClientOptions( Map> headerSuppliers, OkHttpClient httpClient, boolean ownsHttpClient, + AtomicBoolean closed, + Set openWebSockets, int timeout, int maxRetries, Optional initialRetryDelayMillis, @@ -62,6 +72,8 @@ private ClientOptions( this.headerSuppliers = headerSuppliers; this.httpClient = httpClient; this.ownsHttpClient = ownsHttpClient; + this.closed = closed; + this.openWebSockets = openWebSockets; this.timeout = timeout; this.maxRetries = maxRetries; this.initialRetryDelayMillis = initialRetryDelayMillis; @@ -132,12 +144,29 @@ public Optional retryJitterFactor() { * OkHttpClient itself; an OkHttpClient supplied via httpClient is left running, since the * caller owns its lifecycle. *

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

+ * WebSocket clients created from this client that are still connected are disconnected + * first (whether or not the OkHttpClient is owned), so they stop reconnecting before the + * dispatcher goes away. Any WebSocket connect or reconnect attempted afterwards fails with + * an {@code IllegalStateException} explaining that the client has been closed. */ public void close() { + if (!this.closed.compareAndSet(false, true)) { + return; + } + for (AutoCloseable webSocket : new ArrayList<>(this.openWebSockets)) { + try { + webSocket.close(); + } catch (Exception e) { + // best effort; keep closing the remaining sockets and the HTTP client + } + } + this.openWebSockets.clear(); if (!this.ownsHttpClient) { return; } @@ -145,6 +174,33 @@ public void close() { this.httpClient.connectionPool().evictAll(); } + /** + * Returns whether {@link #close()} has been called on this client. + */ + public boolean isClosed() { + return this.closed.get(); + } + + /** + * Tracks a connected WebSocket client so that {@link #close()} disconnects it. + * + * @throws IllegalStateException if this client has already been closed + */ + public void registerWebSocket(AutoCloseable webSocket) { + this.openWebSockets.add(webSocket); + if (this.closed.get()) { + this.openWebSockets.remove(webSocket); + throw new IllegalStateException("root client has been closed"); + } + } + + /** + * Stops tracking a WebSocket client that has been disconnected. + */ + public void unregisterWebSocket(AutoCloseable webSocket) { + this.openWebSockets.remove(webSocket); + } + public Optional webSocketFactory() { return this.webSocketFactory; } @@ -178,6 +234,10 @@ public static class Builder { private boolean ownsHttpClient = true; + private AtomicBoolean closed; + + private Set openWebSockets; + private Optional logging = Optional.empty(); private Optional webSocketFactory = Optional.empty(); @@ -303,12 +363,18 @@ public ClientOptions build() { this.httpClient = httpClientBuilder.build(); this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + AtomicBoolean closedToUse = this.closed != null ? this.closed : new AtomicBoolean(false); + Set openWebSocketsToUse = + this.openWebSockets != null ? this.openWebSockets : ConcurrentHashMap.newKeySet(); + return new ClientOptions( environment, headers, headerSuppliers, httpClient, this.ownsHttpClient, + closedToUse, + openWebSocketsToUse, this.timeout.get(), this.maxRetries, this.initialRetryDelayMillis, @@ -327,6 +393,8 @@ public static Builder from(ClientOptions clientOptions) { builder.timeout = Optional.of(clientOptions.timeout(null)); builder.httpClient = clientOptions.httpClient(); builder.ownsHttpClient = clientOptions.ownsHttpClient; + builder.closed = clientOptions.closed; + builder.openWebSockets = clientOptions.openWebSockets; builder.headers.putAll(clientOptions.headers); builder.headerSuppliers.putAll(clientOptions.headerSuppliers); builder.maxRetries = clientOptions.maxRetries(); diff --git a/src/main/java/com/deepgram/core/OkHttpWebSocketFactory.java b/src/main/java/com/deepgram/core/OkHttpWebSocketFactory.java index 70e6c868..30653bcf 100644 --- a/src/main/java/com/deepgram/core/OkHttpWebSocketFactory.java +++ b/src/main/java/com/deepgram/core/OkHttpWebSocketFactory.java @@ -3,6 +3,7 @@ */ package com.deepgram.core; +import java.util.function.BooleanSupplier; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.WebSocket; @@ -15,17 +16,38 @@ public final class OkHttpWebSocketFactory implements WebSocketFactory { private final OkHttpClient okHttpClient; + private final BooleanSupplier closedCheck; + /** * Creates a new OkHttpWebSocketFactory with the specified OkHttpClient. * * @param okHttpClient The OkHttpClient instance to use for creating WebSockets */ public OkHttpWebSocketFactory(OkHttpClient okHttpClient) { + this(okHttpClient, () -> false); + } + + /** + * Creates a new OkHttpWebSocketFactory that refuses to open WebSockets once the owning + * client has been closed. + * + * @param okHttpClient The OkHttpClient instance to use for creating WebSockets + * @param closedCheck Returns true once the owning client has been closed (e.g. + * {@code clientOptions::isClosed}) + */ + public OkHttpWebSocketFactory(OkHttpClient okHttpClient, BooleanSupplier closedCheck) { this.okHttpClient = okHttpClient; + this.closedCheck = closedCheck; } + /** + * @throws IllegalStateException if the owning client has been closed + */ @Override public WebSocket create(Request request, WebSocketListener listener) { + if (closedCheck.getAsBoolean()) { + throw new IllegalStateException("root client has been closed"); + } return okHttpClient.newWebSocket(request, listener); } } diff --git a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java index 7f6d9df6..294316bb 100644 --- a/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java +++ b/src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java @@ -14,6 +14,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Supplier; import okhttp3.Response; @@ -49,6 +50,8 @@ public abstract class ReconnectingWebSocketListener extends WebSocketListener { private final Supplier connectionSupplier; + private final BooleanSupplier closedCheck; + /** * Creates a new reconnecting WebSocket listener. * @@ -57,9 +60,26 @@ public abstract class ReconnectingWebSocketListener extends WebSocketListener { */ public ReconnectingWebSocketListener( ReconnectingWebSocketListener.ReconnectOptions options, Supplier connectionSupplier) { + this(options, connectionSupplier, () -> false); + } + + /** + * Creates a new reconnecting WebSocket listener. + * + * @param options Reconnection configuration options + * @param connectionSupplier Supplier that creates new WebSocket connections + * @param closedCheck Returns true once the owning client has been closed; connect and + * reconnect attempts made after that point fail with an {@link IllegalStateException} + * instead of being retried + */ + public ReconnectingWebSocketListener( + ReconnectingWebSocketListener.ReconnectOptions options, + Supplier connectionSupplier, + BooleanSupplier closedCheck) { this.activeOptions = options; this.maxEnqueuedMessages = options.maxEnqueuedMessages; this.connectionSupplier = connectionSupplier; + this.closedCheck = closedCheck; } /** @@ -85,11 +105,18 @@ public void applyOptionsOverride(ReconnectOptions options) { * - TimeoutException: Includes retry attempt context * - InterruptedException: Preserves thread interruption status * - ExecutionException: Extracts actual cause and adds context + * - Owning client closed: reports an IllegalStateException once and stops reconnecting */ public void connect() { if (!connectLock.compareAndSet(false, true)) { return; } + if (closedCheck.getAsBoolean()) { + shouldReconnect.set(false); + connectLock.set(false); + onWebSocketFailure(null, new IllegalStateException("root client has been closed"), null); + return; + } ReconnectOptions options = this.activeOptions; if (retryCount.get() > options.maxRetries) { connectLock.set(false); @@ -391,10 +418,10 @@ protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, Strin * - 2+ = exponential backoff up to maxReconnectionDelayMs */ private long getNextDelay() { - ReconnectOptions options = this.activeOptions; if (retryCount.get() == 1) { - return options.minReconnectionDelayMs; + return activeOptions.minReconnectionDelayMs; } + ReconnectOptions options = this.activeOptions; long delay = (long) (options.minReconnectionDelayMs * Math.pow(options.reconnectionDelayGrowFactor, retryCount.get() - 1)); return Math.min(delay, options.maxReconnectionDelayMs); @@ -403,8 +430,13 @@ private long getNextDelay() { /** * Schedules a reconnection attempt with appropriate delay. * Increments retry count and uses exponential backoff. + * Does nothing once the owning client has been closed. */ private void scheduleReconnect() { + if (closedCheck.getAsBoolean()) { + shouldReconnect.set(false); + return; + } retryCount.incrementAndGet(); long delay = getNextDelay(); reconnectExecutor.schedule(this::connect, delay, MILLISECONDS); diff --git a/src/main/java/com/deepgram/core/RetryInterceptor.java b/src/main/java/com/deepgram/core/RetryInterceptor.java index a0959bbc..aad032cd 100644 --- a/src/main/java/com/deepgram/core/RetryInterceptor.java +++ b/src/main/java/com/deepgram/core/RetryInterceptor.java @@ -10,9 +10,13 @@ import java.time.format.DateTimeParseException; import java.util.Optional; import java.util.Random; +import okhttp3.Call; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.AsyncTimeout; +import okio.Timeout; public class RetryInterceptor implements Interceptor { @@ -78,13 +82,28 @@ private Response retryChain(Response response, Chain chain, int maxRetries) thro ExponentialBackoff backoff = new ExponentialBackoff(maxRetries); Optional nextBackoff = backoff.nextBackoff(response); while (nextBackoff.isPresent()) { + response = bufferResponse(response); + Optional callTimeout = suspendCallTimeout(chain); try { Thread.sleep(nextBackoff.get().toMillis()); } catch (InterruptedException e) { + response.close(); throw new IOException("Interrupted while trying request", e); + } finally { + callTimeout.ifPresent(AsyncTimeout::enter); + } + Response nextResponse; + try { + nextResponse = chain.proceed(chain.request()); + } catch (IOException e) { + // A retry attempt failed with a transport error (connection reset, unreachable host, ...). The + // error is intentionally dropped: the response already received from the API is more actionable. + // Note that a call-timeout expiry or an explicit cancel() also ends up here, but OkHttp then + // discards whatever the interceptor chain returns and reports the cancellation itself. + return response; } response.close(); - response = chain.proceed(chain.request()); + response = nextResponse; if (shouldRetry(response.code())) { nextBackoff = backoff.nextBackoff(response); } else { @@ -95,6 +114,51 @@ private Response retryChain(Response response, Chain chain, int maxRetries) thro return response; } + /** + * Pauses the OkHttp call timeout so that it applies to each attempt individually rather than to the whole + * retry loop: the backoff wait (e.g. a {@code Retry-After} the size of the call timeout) does not consume the + * budget of the next attempt, and each attempt starts with a fresh budget once {@link AsyncTimeout#enter()} + * is called again. + * + *

{@link AsyncTimeout#exit()} and {@link AsyncTimeout#enter()} must stay strictly paired: {@code exit()} on + * a timeout that is not armed is a no-op, but {@code enter()} on one that is already armed throws + * {@code IllegalStateException}. Every caller must re-enter the returned timeout exactly once, on all paths. + */ + private static Optional suspendCallTimeout(Chain chain) { + Call call = chain.call(); + if (call == null) { + return Optional.empty(); + } + Timeout timeout = call.timeout(); + if (!(timeout instanceof AsyncTimeout)) { + return Optional.empty(); + } + AsyncTimeout callTimeout = (AsyncTimeout) timeout; + callTimeout.exit(); + return Optional.of(callTimeout); + } + + /** + * Reads the body into memory so the response stays usable after the underlying connection + * is released or cancelled by a later attempt. + */ + private static Response bufferResponse(Response response) { + ResponseBody body = response.body(); + if (body == null) { + return response; + } + try { + byte[] bytes = body.bytes(); + Response buffered = response.newBuilder() + .body(ResponseBody.create(bytes, body.contentType())) + .build(); + response.close(); + return buffered; + } catch (IOException e) { + return response; + } + } + /** * Calculates the retry delay from response headers, with fallback to exponential backoff. * Priority: Retry-After > X-RateLimit-Reset > Exponential Backoff diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java index b6ee9000..5962f4bf 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1AgentAudioDone.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; diff --git a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java index 11d0d8d0..9bd5f769 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; 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 588aad18..f82f59ee 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; 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 3de75ec5..43b8628b 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ListenUpdated.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; 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 2b2f7946..e6efced0 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; 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 1c21ce70..393f1f91 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.types.DeepgramListenProviderV1; import com.deepgram.types.DeepgramListenProviderV2; import com.fasterxml.jackson.annotation.JsonCreator; 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 06e47e60..345ba2f3 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.types.DeepgramListenProviderV1; import com.deepgram.types.DeepgramListenProviderV2; import com.fasterxml.jackson.annotation.JsonCreator; 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 443c2148..ad1fdb2f 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsApplied.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; 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 87c8d7d1..8b134b54 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SpeakUpdated.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; 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 4b6f6230..2d67cad1 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; 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 d0390e2d..e739aec6 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.types.DeepgramListenProviderV1; import com.deepgram.types.DeepgramListenProviderV2; import com.fasterxml.jackson.annotation.JsonCreator; 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 36e8a9d7..3556d319 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java +++ b/src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UserStartedSpeaking.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; diff --git a/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java index d8305b17..5bf4d734 100644 --- a/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.agent.v1.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; @@ -162,13 +160,22 @@ public CompletableFuture connect() { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { readyState = WebSocketReadyState.OPEN; @@ -207,6 +214,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { } } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -215,6 +223,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } diff --git a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java index ffaa903e..278a2c9b 100644 --- a/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.listen.v1.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; @@ -229,13 +227,22 @@ public CompletableFuture connect(V1ConnectOptions options) { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { readyState = WebSocketReadyState.OPEN; @@ -270,6 +277,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { } } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -278,6 +286,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java index 8f7496d5..c9f2f357 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.listen.v2.types; -// Manual patch - see .fernignore. - import com.deepgram.core.ObjectMappers; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; diff --git a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureFailure.java b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureFailure.java index 478307b8..a48faaca 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureFailure.java +++ b/src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ConfigureFailure.java @@ -10,10 +10,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @@ -23,11 +25,22 @@ public final class ListenV2ConfigureFailure { private final int sequenceId; + private final Optional code; + + private final Optional description; + private final Map additionalProperties; - private ListenV2ConfigureFailure(String requestId, int sequenceId, Map additionalProperties) { + private ListenV2ConfigureFailure( + String requestId, + int sequenceId, + Optional code, + Optional description, + Map additionalProperties) { this.requestId = requestId; this.sequenceId = sequenceId; + this.code = code; + this.description = description; this.additionalProperties = additionalProperties; } @@ -57,6 +70,22 @@ public int getSequenceId() { return sequenceId; } + /** + * @return Failure code identifying the rejected configuration + */ + @JsonProperty("code") + public Optional getCode() { + return code; + } + + /** + * @return A human-readable description of the configuration failure + */ + @JsonProperty("description") + public Optional getDescription() { + return description; + } + @java.lang.Override public boolean equals(Object other) { if (this == other) return true; @@ -69,12 +98,15 @@ public Map getAdditionalProperties() { } private boolean equalTo(ListenV2ConfigureFailure other) { - return requestId.equals(other.requestId) && sequenceId == other.sequenceId; + return requestId.equals(other.requestId) + && sequenceId == other.sequenceId + && code.equals(other.code) + && description.equals(other.description); } @java.lang.Override public int hashCode() { - return Objects.hash(this.requestId, this.sequenceId); + return Objects.hash(this.requestId, this.sequenceId, this.code, this.description); } @java.lang.Override @@ -110,6 +142,20 @@ public interface _FinalStage { _FinalStage additionalProperty(String key, Object value); _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Failure code identifying the rejected configuration

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

A human-readable description of the configuration failure

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

A human-readable description of the configuration failure

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

A human-readable description of the configuration failure

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

Failure code identifying the rejected configuration

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

Failure code identifying the rejected configuration

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

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

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

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

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

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

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

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

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

The unique identifier of the request

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

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

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

Warning code identifying the condition, in SCREAMING_SNAKE_CASE

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

A human-readable description of the warning

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

The unique identifier of the request

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

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

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

Warning code identifying the condition, in SCREAMING_SNAKE_CASE

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

A human-readable description of the warning

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("description") + public _FinalStage description(@NotNull String description) { + this.description = Objects.requireNonNull(description, "description must not be null"); + return this; + } + + @java.lang.Override + public ListenV2Warning build() { + return new ListenV2Warning(requestId, sequenceId, code, description, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java index b2dee9b4..5f55fea3 100644 --- a/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.listen.v2.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; @@ -20,6 +18,7 @@ import com.deepgram.resources.listen.v2.types.ListenV2FatalError; import com.deepgram.resources.listen.v2.types.ListenV2ForceEndTurn; import com.deepgram.resources.listen.v2.types.ListenV2TurnInfo; +import com.deepgram.resources.listen.v2.types.ListenV2Warning; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.concurrent.CompletableFuture; @@ -72,6 +71,8 @@ public class V2WebSocketClient implements AutoCloseable { private volatile Consumer configureFailureHandler; + private volatile Consumer warningHandler; + private volatile Consumer errorHandler; /** @@ -175,13 +176,22 @@ public CompletableFuture connect(V2ConnectOptions options) { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { closeStreamSocket.set(null); @@ -223,6 +233,7 @@ protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, Strin && (code != 1005 || closeStreamSocket.get() != webSocket); } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -231,6 +242,7 @@ protected boolean shouldReconnectAfterClose(WebSocket webSocket, int code, Strin * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } @@ -281,11 +293,6 @@ public CompletableFuture sendCloseStream(ListenV2CloseStream message) { /** * Sends a ListenV2ForceEndTurn message to the server asynchronously. - * - *

Sending this with no turn in progress is not an error. The server answers with a - * {@code Warning} frame carrying code {@code FORCE_END_TURN_NO_ACTIVE_TURN} and ignores the - * request. Listen V2 has no typed warning event yet, so that frame is delivered as raw JSON - * to {@link #onMessage(java.util.function.Consumer)}. * @param message the message to send * @return a CompletableFuture that completes when the message is sent */ @@ -334,6 +341,14 @@ public void onConfigureFailure(Consumer handler) { this.configureFailureHandler = handler; } + /** + * Registers a handler for ListenV2Warning messages from the server. + * @param handler the handler to invoke when a message is received + */ + public void onWarning(Consumer handler) { + this.warningHandler = handler; + } + /** * Registers a handler for ListenV2FatalError messages from the server. * @param handler the handler to invoke when a message is received @@ -351,8 +366,7 @@ public void onConnected(Runnable handler) { } /** - * Registers a handler called when the connection is closed. An empty peer close frame is - * reported with code 1005, even though the SDK acknowledges it with code 1000. + * Registers a handler called when the connection is closed. * @param handler the handler to invoke when disconnected */ public void onDisconnected(Consumer handler) { @@ -475,6 +489,23 @@ private void handleIncomingMessage(String json) { return; } } + if (node.has("request_id") + && node.has("sequence_id") + && node.has("code") + && node.has("description") + && "Warning".equals(node.path("type").asText())) { + ListenV2Warning warningHandlerEvent = null; + try { + warningHandlerEvent = objectMapper.treeToValue(node, ListenV2Warning.class); + } catch (Exception e) { + } + if (warningHandlerEvent != null) { + if (warningHandler != null) { + warningHandler.accept(warningHandlerEvent); + } + return; + } + } if (node.has("sequence_id") && node.has("code") && node.has("description") 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 ae271f2a..e6824a38 100644 --- a/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.speak.v1.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; @@ -140,13 +138,22 @@ public CompletableFuture connect(V1ConnectOptions options) { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { readyState = WebSocketReadyState.OPEN; @@ -185,6 +192,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { } } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -201,6 +209,7 @@ public CompletableFuture connect() { * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } diff --git a/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java b/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java index bc3fb63e..d1a3d823 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java +++ b/src/main/java/com/deepgram/resources/speak/v2/audio/requests/SpeakV2Request.java @@ -167,7 +167,7 @@ public Optional getSampleRate() { } /** - * @return Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages. + * @return Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Accepted values run 0.5 to 1.5 in 0.05 increments. Not yet supported in all languages. When the text contains an inline pause marker, speed is capped at 1.15 (PAUSE_SPEED_CAP_EXCEEDED above that). A value other than 1.0 cannot be combined with inline pronunciation controls (CONTROL_COMBINATION_INVALID). */ @JsonIgnore public Optional getSpeed() { @@ -183,7 +183,7 @@ public Optional getPriority() { } /** - * @return The text content to be converted to speech. The server normalizes and preprocesses the text before synthesis. Inline pause and pronunciation controls are not yet applied; they are stripped from the text before synthesis. + * @return The text content to be converted to speech. The server normalizes and preprocesses the text before synthesis. May contain inline pause controls (\{pause:500ms\}, 500-3000 ms in 100 ms steps, at most 8 per request) and inline pronunciation controls (\{"word": "...", "pronounce": "<IPA>"\}, Early Access). Pronunciation cannot be combined with pause or with a speed other than 1.0, and speed is capped at 1.15 when a pause is present. See Speed, Pause, Pronunciation. */ @JsonProperty("text") public String getText() { @@ -255,7 +255,7 @@ public interface ModelStage { public interface TextStage { /** - *

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

- *

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

+ *

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

+ *

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

*

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

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

Warning code identifying the condition, in SCREAMING_SNAKE_CASE.

*

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

- *

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

+ *

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

+ *

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

*

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

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

Warning code identifying the condition, in SCREAMING_SNAKE_CASE.

*

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

- *

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

+ *

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

+ *

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

*

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

* @return Reference to {@code this} so that method calls can be chained together. */ diff --git a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java index a94145db..b4e01f7a 100644 --- a/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java +++ b/src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java @@ -3,8 +3,6 @@ */ package com.deepgram.resources.speak.v2.websocket; -// Manual patch - see .fernignore. - import com.deepgram.core.ClientOptions; import com.deepgram.core.DisconnectReason; import com.deepgram.core.ObjectMappers; @@ -163,13 +161,22 @@ public CompletableFuture connect(V2ConnectOptions options) { ? this.reconnectOptions : ReconnectingWebSocketListener.ReconnectOptions.builder().build(); this.reconnectingListener = - new ReconnectingWebSocketListener(reconnectOpts, () -> { - if (clientOptions.webSocketFactory().isPresent()) { - return clientOptions.webSocketFactory().get().create(request, this.reconnectingListener); - } else { - return okHttpClient.newWebSocket(request, this.reconnectingListener); - } - }) { + new ReconnectingWebSocketListener( + reconnectOpts, + () -> { + if (clientOptions.isClosed()) { + throw new IllegalStateException("root client has been closed"); + } + if (clientOptions.webSocketFactory().isPresent()) { + return clientOptions + .webSocketFactory() + .get() + .create(request, this.reconnectingListener); + } else { + return okHttpClient.newWebSocket(request, this.reconnectingListener); + } + }, + clientOptions::isClosed) { @Override protected void onWebSocketOpen(WebSocket webSocket, Response response) { readyState = WebSocketReadyState.OPEN; @@ -208,6 +215,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { } } }; + clientOptions.registerWebSocket(this); reconnectingListener.connect(); return connectionFuture; } @@ -216,6 +224,7 @@ protected void onWebSocketClosed(WebSocket webSocket, int code, String reason) { * Disconnects the WebSocket connection and releases resources. */ public void disconnect() { + clientOptions.unregisterWebSocket(this); if (reconnectingListener != null) { reconnectingListener.disconnect(); } diff --git a/src/main/java/com/deepgram/types/DeepgramModel.java b/src/main/java/com/deepgram/types/DeepgramModel.java index 14a797cc..6444d7f7 100644 --- a/src/main/java/com/deepgram/types/DeepgramModel.java +++ b/src/main/java/com/deepgram/types/DeepgramModel.java @@ -3,8 +3,6 @@ */ package com.deepgram.types; -// Manual patch - see .fernignore. - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/src/main/java/com/deepgram/types/SharedIntents.java b/src/main/java/com/deepgram/types/SharedIntents.java index c689861c..05799c25 100644 --- a/src/main/java/com/deepgram/types/SharedIntents.java +++ b/src/main/java/com/deepgram/types/SharedIntents.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -20,18 +21,19 @@ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = SharedIntents.Builder.class) public final class SharedIntents { - private final Optional results; + private final Optional> segments; private final Map additionalProperties; - private SharedIntents(Optional results, Map additionalProperties) { - this.results = results; + private SharedIntents( + Optional> segments, Map additionalProperties) { + this.segments = segments; this.additionalProperties = additionalProperties; } - @JsonProperty("results") - public Optional getResults() { - return results; + @JsonProperty("segments") + public Optional> getSegments() { + return segments; } @java.lang.Override @@ -46,12 +48,12 @@ public Map getAdditionalProperties() { } private boolean equalTo(SharedIntents other) { - return results.equals(other.results); + return segments.equals(other.segments); } @java.lang.Override public int hashCode() { - return Objects.hash(this.results); + return Objects.hash(this.segments); } @java.lang.Override @@ -65,7 +67,7 @@ public static Builder builder() { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder { - private Optional results = Optional.empty(); + private Optional> segments = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -73,23 +75,23 @@ public static final class Builder { private Builder() {} public Builder from(SharedIntents other) { - results(other.getResults()); + segments(other.getSegments()); return this; } - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public Builder results(Optional results) { - this.results = results; + @JsonSetter(value = "segments", nulls = Nulls.SKIP) + public Builder segments(Optional> segments) { + this.segments = segments; return this; } - public Builder results(SharedIntentsResults results) { - this.results = Optional.ofNullable(results); + public Builder segments(List segments) { + this.segments = Optional.ofNullable(segments); return this; } public SharedIntents build() { - return new SharedIntents(results, additionalProperties); + return new SharedIntents(segments, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedIntentsResults.java b/src/main/java/com/deepgram/types/SharedIntentsResults.java deleted file mode 100644 index 44bfe377..00000000 --- a/src/main/java/com/deepgram/types/SharedIntentsResults.java +++ /dev/null @@ -1,106 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedIntentsResults.Builder.class) -public final class SharedIntentsResults { - private final Optional intents; - - private final Map additionalProperties; - - private SharedIntentsResults( - Optional intents, Map additionalProperties) { - this.intents = intents; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("intents") - public Optional getIntents() { - return intents; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SharedIntentsResults && equalTo((SharedIntentsResults) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SharedIntentsResults other) { - return intents.equals(other.intents); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.intents); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional intents = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SharedIntentsResults other) { - intents(other.getIntents()); - return this; - } - - @JsonSetter(value = "intents", nulls = Nulls.SKIP) - public Builder intents(Optional intents) { - this.intents = intents; - return this; - } - - public Builder intents(SharedIntentsResultsIntents intents) { - this.intents = Optional.ofNullable(intents); - return this; - } - - public SharedIntentsResults build() { - return new SharedIntentsResults(intents, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/types/SharedIntentsResultsIntents.java b/src/main/java/com/deepgram/types/SharedIntentsResultsIntents.java deleted file mode 100644 index a5fe89b0..00000000 --- a/src/main/java/com/deepgram/types/SharedIntentsResultsIntents.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedIntentsResultsIntents.Builder.class) -public final class SharedIntentsResultsIntents { - private final Optional> segments; - - private final Map additionalProperties; - - private SharedIntentsResultsIntents( - Optional> segments, - Map additionalProperties) { - this.segments = segments; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("segments") - public Optional> getSegments() { - return segments; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SharedIntentsResultsIntents && equalTo((SharedIntentsResultsIntents) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SharedIntentsResultsIntents other) { - return segments.equals(other.segments); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.segments); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional> segments = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SharedIntentsResultsIntents other) { - segments(other.getSegments()); - return this; - } - - @JsonSetter(value = "segments", nulls = Nulls.SKIP) - public Builder segments(Optional> segments) { - this.segments = segments; - return this; - } - - public Builder segments(List segments) { - this.segments = Optional.ofNullable(segments); - return this; - } - - public SharedIntentsResultsIntents build() { - return new SharedIntentsResultsIntents(segments, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItem.java b/src/main/java/com/deepgram/types/SharedIntentsSegmentsItem.java similarity index 78% rename from src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItem.java rename to src/main/java/com/deepgram/types/SharedIntentsSegmentsItem.java index 694be4f6..431b68f0 100644 --- a/src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItem.java +++ b/src/main/java/com/deepgram/types/SharedIntentsSegmentsItem.java @@ -19,23 +19,23 @@ import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedIntentsResultsIntentsSegmentsItem.Builder.class) -public final class SharedIntentsResultsIntentsSegmentsItem { +@JsonDeserialize(builder = SharedIntentsSegmentsItem.Builder.class) +public final class SharedIntentsSegmentsItem { private final Optional text; private final Optional startWord; private final Optional endWord; - private final Optional> intents; + private final Optional> intents; private final Map additionalProperties; - private SharedIntentsResultsIntentsSegmentsItem( + private SharedIntentsSegmentsItem( Optional text, Optional startWord, Optional endWord, - Optional> intents, + Optional> intents, Map additionalProperties) { this.text = text; this.startWord = startWord; @@ -60,15 +60,14 @@ public Optional getEndWord() { } @JsonProperty("intents") - public Optional> getIntents() { + public Optional> getIntents() { return intents; } @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof SharedIntentsResultsIntentsSegmentsItem - && equalTo((SharedIntentsResultsIntentsSegmentsItem) other); + return other instanceof SharedIntentsSegmentsItem && equalTo((SharedIntentsSegmentsItem) other); } @JsonAnyGetter @@ -76,7 +75,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(SharedIntentsResultsIntentsSegmentsItem other) { + private boolean equalTo(SharedIntentsSegmentsItem other) { return text.equals(other.text) && startWord.equals(other.startWord) && endWord.equals(other.endWord) @@ -105,14 +104,14 @@ public static final class Builder { private Optional endWord = Optional.empty(); - private Optional> intents = Optional.empty(); + private Optional> intents = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); private Builder() {} - public Builder from(SharedIntentsResultsIntentsSegmentsItem other) { + public Builder from(SharedIntentsSegmentsItem other) { text(other.getText()); startWord(other.getStartWord()); endWord(other.getEndWord()); @@ -154,18 +153,18 @@ public Builder endWord(Double endWord) { } @JsonSetter(value = "intents", nulls = Nulls.SKIP) - public Builder intents(Optional> intents) { + public Builder intents(Optional> intents) { this.intents = intents; return this; } - public Builder intents(List intents) { + public Builder intents(List intents) { this.intents = Optional.ofNullable(intents); return this; } - public SharedIntentsResultsIntentsSegmentsItem build() { - return new SharedIntentsResultsIntentsSegmentsItem(text, startWord, endWord, intents, additionalProperties); + public SharedIntentsSegmentsItem build() { + return new SharedIntentsSegmentsItem(text, startWord, endWord, intents, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItemIntentsItem.java b/src/main/java/com/deepgram/types/SharedIntentsSegmentsItemIntentsItem.java similarity index 81% rename from src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItemIntentsItem.java rename to src/main/java/com/deepgram/types/SharedIntentsSegmentsItemIntentsItem.java index d6554e2b..9f97c919 100644 --- a/src/main/java/com/deepgram/types/SharedIntentsResultsIntentsSegmentsItemIntentsItem.java +++ b/src/main/java/com/deepgram/types/SharedIntentsSegmentsItemIntentsItem.java @@ -18,15 +18,15 @@ import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedIntentsResultsIntentsSegmentsItemIntentsItem.Builder.class) -public final class SharedIntentsResultsIntentsSegmentsItemIntentsItem { +@JsonDeserialize(builder = SharedIntentsSegmentsItemIntentsItem.Builder.class) +public final class SharedIntentsSegmentsItemIntentsItem { private final Optional intent; private final Optional confidenceScore; private final Map additionalProperties; - private SharedIntentsResultsIntentsSegmentsItemIntentsItem( + private SharedIntentsSegmentsItemIntentsItem( Optional intent, Optional confidenceScore, Map additionalProperties) { this.intent = intent; this.confidenceScore = confidenceScore; @@ -46,8 +46,8 @@ public Optional getConfidenceScore() { @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof SharedIntentsResultsIntentsSegmentsItemIntentsItem - && equalTo((SharedIntentsResultsIntentsSegmentsItemIntentsItem) other); + return other instanceof SharedIntentsSegmentsItemIntentsItem + && equalTo((SharedIntentsSegmentsItemIntentsItem) other); } @JsonAnyGetter @@ -55,7 +55,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(SharedIntentsResultsIntentsSegmentsItemIntentsItem other) { + private boolean equalTo(SharedIntentsSegmentsItemIntentsItem other) { return intent.equals(other.intent) && confidenceScore.equals(other.confidenceScore); } @@ -84,7 +84,7 @@ public static final class Builder { private Builder() {} - public Builder from(SharedIntentsResultsIntentsSegmentsItemIntentsItem other) { + public Builder from(SharedIntentsSegmentsItemIntentsItem other) { intent(other.getIntent()); confidenceScore(other.getConfidenceScore()); return this; @@ -112,9 +112,8 @@ public Builder confidenceScore(Float confidenceScore) { return this; } - public SharedIntentsResultsIntentsSegmentsItemIntentsItem build() { - return new SharedIntentsResultsIntentsSegmentsItemIntentsItem( - intent, confidenceScore, additionalProperties); + public SharedIntentsSegmentsItemIntentsItem build() { + return new SharedIntentsSegmentsItemIntentsItem(intent, confidenceScore, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedTopics.java b/src/main/java/com/deepgram/types/SharedTopics.java index 463e84ca..25a75ea4 100644 --- a/src/main/java/com/deepgram/types/SharedTopics.java +++ b/src/main/java/com/deepgram/types/SharedTopics.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -20,18 +21,18 @@ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = SharedTopics.Builder.class) public final class SharedTopics { - private final Optional results; + private final Optional> segments; private final Map additionalProperties; - private SharedTopics(Optional results, Map additionalProperties) { - this.results = results; + private SharedTopics(Optional> segments, Map additionalProperties) { + this.segments = segments; this.additionalProperties = additionalProperties; } - @JsonProperty("results") - public Optional getResults() { - return results; + @JsonProperty("segments") + public Optional> getSegments() { + return segments; } @java.lang.Override @@ -46,12 +47,12 @@ public Map getAdditionalProperties() { } private boolean equalTo(SharedTopics other) { - return results.equals(other.results); + return segments.equals(other.segments); } @java.lang.Override public int hashCode() { - return Objects.hash(this.results); + return Objects.hash(this.segments); } @java.lang.Override @@ -65,7 +66,7 @@ public static Builder builder() { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder { - private Optional results = Optional.empty(); + private Optional> segments = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -73,23 +74,23 @@ public static final class Builder { private Builder() {} public Builder from(SharedTopics other) { - results(other.getResults()); + segments(other.getSegments()); return this; } - @JsonSetter(value = "results", nulls = Nulls.SKIP) - public Builder results(Optional results) { - this.results = results; + @JsonSetter(value = "segments", nulls = Nulls.SKIP) + public Builder segments(Optional> segments) { + this.segments = segments; return this; } - public Builder results(SharedTopicsResults results) { - this.results = Optional.ofNullable(results); + public Builder segments(List segments) { + this.segments = Optional.ofNullable(segments); return this; } public SharedTopics build() { - return new SharedTopics(results, additionalProperties); + return new SharedTopics(segments, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedTopicsResults.java b/src/main/java/com/deepgram/types/SharedTopicsResults.java deleted file mode 100644 index d25c8cc9..00000000 --- a/src/main/java/com/deepgram/types/SharedTopicsResults.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedTopicsResults.Builder.class) -public final class SharedTopicsResults { - private final Optional topics; - - private final Map additionalProperties; - - private SharedTopicsResults(Optional topics, Map additionalProperties) { - this.topics = topics; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("topics") - public Optional getTopics() { - return topics; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SharedTopicsResults && equalTo((SharedTopicsResults) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SharedTopicsResults other) { - return topics.equals(other.topics); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.topics); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional topics = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SharedTopicsResults other) { - topics(other.getTopics()); - return this; - } - - @JsonSetter(value = "topics", nulls = Nulls.SKIP) - public Builder topics(Optional topics) { - this.topics = topics; - return this; - } - - public Builder topics(SharedTopicsResultsTopics topics) { - this.topics = Optional.ofNullable(topics); - return this; - } - - public SharedTopicsResults build() { - return new SharedTopicsResults(topics, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/types/SharedTopicsResultsTopics.java b/src/main/java/com/deepgram/types/SharedTopicsResultsTopics.java deleted file mode 100644 index e3a1cbba..00000000 --- a/src/main/java/com/deepgram/types/SharedTopicsResultsTopics.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.deepgram.types; - -import com.deepgram.core.ObjectMappers; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedTopicsResultsTopics.Builder.class) -public final class SharedTopicsResultsTopics { - private final Optional> segments; - - private final Map additionalProperties; - - private SharedTopicsResultsTopics( - Optional> segments, Map additionalProperties) { - this.segments = segments; - this.additionalProperties = additionalProperties; - } - - @JsonProperty("segments") - public Optional> getSegments() { - return segments; - } - - @java.lang.Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof SharedTopicsResultsTopics && equalTo((SharedTopicsResultsTopics) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(SharedTopicsResultsTopics other) { - return segments.equals(other.segments); - } - - @java.lang.Override - public int hashCode() { - return Objects.hash(this.segments); - } - - @java.lang.Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional> segments = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(SharedTopicsResultsTopics other) { - segments(other.getSegments()); - return this; - } - - @JsonSetter(value = "segments", nulls = Nulls.SKIP) - public Builder segments(Optional> segments) { - this.segments = segments; - return this; - } - - public Builder segments(List segments) { - this.segments = Optional.ofNullable(segments); - return this; - } - - public SharedTopicsResultsTopics build() { - return new SharedTopicsResultsTopics(segments, additionalProperties); - } - - public Builder additionalProperty(String key, Object value) { - this.additionalProperties.put(key, value); - return this; - } - - public Builder additionalProperties(Map additionalProperties) { - this.additionalProperties.putAll(additionalProperties); - return this; - } - } -} diff --git a/src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItem.java b/src/main/java/com/deepgram/types/SharedTopicsSegmentsItem.java similarity index 78% rename from src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItem.java rename to src/main/java/com/deepgram/types/SharedTopicsSegmentsItem.java index 19cd942e..b479076e 100644 --- a/src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItem.java +++ b/src/main/java/com/deepgram/types/SharedTopicsSegmentsItem.java @@ -19,23 +19,23 @@ import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedTopicsResultsTopicsSegmentsItem.Builder.class) -public final class SharedTopicsResultsTopicsSegmentsItem { +@JsonDeserialize(builder = SharedTopicsSegmentsItem.Builder.class) +public final class SharedTopicsSegmentsItem { private final Optional text; private final Optional startWord; private final Optional endWord; - private final Optional> topics; + private final Optional> topics; private final Map additionalProperties; - private SharedTopicsResultsTopicsSegmentsItem( + private SharedTopicsSegmentsItem( Optional text, Optional startWord, Optional endWord, - Optional> topics, + Optional> topics, Map additionalProperties) { this.text = text; this.startWord = startWord; @@ -60,15 +60,14 @@ public Optional getEndWord() { } @JsonProperty("topics") - public Optional> getTopics() { + public Optional> getTopics() { return topics; } @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof SharedTopicsResultsTopicsSegmentsItem - && equalTo((SharedTopicsResultsTopicsSegmentsItem) other); + return other instanceof SharedTopicsSegmentsItem && equalTo((SharedTopicsSegmentsItem) other); } @JsonAnyGetter @@ -76,7 +75,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(SharedTopicsResultsTopicsSegmentsItem other) { + private boolean equalTo(SharedTopicsSegmentsItem other) { return text.equals(other.text) && startWord.equals(other.startWord) && endWord.equals(other.endWord) @@ -105,14 +104,14 @@ public static final class Builder { private Optional endWord = Optional.empty(); - private Optional> topics = Optional.empty(); + private Optional> topics = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); private Builder() {} - public Builder from(SharedTopicsResultsTopicsSegmentsItem other) { + public Builder from(SharedTopicsSegmentsItem other) { text(other.getText()); startWord(other.getStartWord()); endWord(other.getEndWord()); @@ -154,18 +153,18 @@ public Builder endWord(Double endWord) { } @JsonSetter(value = "topics", nulls = Nulls.SKIP) - public Builder topics(Optional> topics) { + public Builder topics(Optional> topics) { this.topics = topics; return this; } - public Builder topics(List topics) { + public Builder topics(List topics) { this.topics = Optional.ofNullable(topics); return this; } - public SharedTopicsResultsTopicsSegmentsItem build() { - return new SharedTopicsResultsTopicsSegmentsItem(text, startWord, endWord, topics, additionalProperties); + public SharedTopicsSegmentsItem build() { + return new SharedTopicsSegmentsItem(text, startWord, endWord, topics, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItemTopicsItem.java b/src/main/java/com/deepgram/types/SharedTopicsSegmentsItemTopicsItem.java similarity index 82% rename from src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItemTopicsItem.java rename to src/main/java/com/deepgram/types/SharedTopicsSegmentsItemTopicsItem.java index 3721cd8b..03464d24 100644 --- a/src/main/java/com/deepgram/types/SharedTopicsResultsTopicsSegmentsItemTopicsItem.java +++ b/src/main/java/com/deepgram/types/SharedTopicsSegmentsItemTopicsItem.java @@ -18,15 +18,15 @@ import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = SharedTopicsResultsTopicsSegmentsItemTopicsItem.Builder.class) -public final class SharedTopicsResultsTopicsSegmentsItemTopicsItem { +@JsonDeserialize(builder = SharedTopicsSegmentsItemTopicsItem.Builder.class) +public final class SharedTopicsSegmentsItemTopicsItem { private final Optional topic; private final Optional confidenceScore; private final Map additionalProperties; - private SharedTopicsResultsTopicsSegmentsItemTopicsItem( + private SharedTopicsSegmentsItemTopicsItem( Optional topic, Optional confidenceScore, Map additionalProperties) { this.topic = topic; this.confidenceScore = confidenceScore; @@ -46,8 +46,8 @@ public Optional getConfidenceScore() { @java.lang.Override public boolean equals(Object other) { if (this == other) return true; - return other instanceof SharedTopicsResultsTopicsSegmentsItemTopicsItem - && equalTo((SharedTopicsResultsTopicsSegmentsItemTopicsItem) other); + return other instanceof SharedTopicsSegmentsItemTopicsItem + && equalTo((SharedTopicsSegmentsItemTopicsItem) other); } @JsonAnyGetter @@ -55,7 +55,7 @@ public Map getAdditionalProperties() { return this.additionalProperties; } - private boolean equalTo(SharedTopicsResultsTopicsSegmentsItemTopicsItem other) { + private boolean equalTo(SharedTopicsSegmentsItemTopicsItem other) { return topic.equals(other.topic) && confidenceScore.equals(other.confidenceScore); } @@ -84,7 +84,7 @@ public static final class Builder { private Builder() {} - public Builder from(SharedTopicsResultsTopicsSegmentsItemTopicsItem other) { + public Builder from(SharedTopicsSegmentsItemTopicsItem other) { topic(other.getTopic()); confidenceScore(other.getConfidenceScore()); return this; @@ -112,8 +112,8 @@ public Builder confidenceScore(Float confidenceScore) { return this; } - public SharedTopicsResultsTopicsSegmentsItemTopicsItem build() { - return new SharedTopicsResultsTopicsSegmentsItemTopicsItem(topic, confidenceScore, additionalProperties); + public SharedTopicsSegmentsItemTopicsItem build() { + return new SharedTopicsSegmentsItemTopicsItem(topic, confidenceScore, additionalProperties); } public Builder additionalProperty(String key, Object value) { diff --git a/src/test/java/com/deepgram/ClientBuilderTest.java b/src/test/java/com/deepgram/ClientBuilderTest.java index 01d0f680..348164c6 100644 --- a/src/test/java/com/deepgram/ClientBuilderTest.java +++ b/src/test/java/com/deepgram/ClientBuilderTest.java @@ -3,8 +3,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.deepgram.core.ClientOptions; import com.deepgram.core.Environment; +import com.deepgram.core.WebSocketFactory; +import com.deepgram.resources.listen.v1.websocket.V1ConnectOptions; +import com.deepgram.resources.listen.v1.websocket.V1WebSocketClient; +import com.deepgram.types.ListenV1Model; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.WebSocket; +import okio.ByteString; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -193,6 +204,74 @@ void closesWebSocketClientsBeforeConnecting() { } } + @Test + @DisplayName("closing the root client closes a connected WebSocket and blocks reconnects") + void closesConnectedWebSocketAndBlocksReconnects() throws Exception { + CountDownLatch closed = new CountDownLatch(1); + AtomicInteger connectionAttempts = new AtomicInteger(); + WebSocket connectedSocket = new WebSocket() { + @Override + public Request request() { + return new Request.Builder().url("http://localhost/").build(); + } + + @Override + public long queueSize() { + return 0; + } + + @Override + public boolean send(String text) { + return true; + } + + @Override + public boolean send(ByteString bytes) { + return true; + } + + @Override + public boolean close(int code, String reason) { + closed.countDown(); + return true; + } + + @Override + public void cancel() {} + }; + WebSocketFactory factory = (request, listener) -> { + connectionAttempts.incrementAndGet(); + listener.onOpen(connectedSocket, null); + return connectedSocket; + }; + DeepgramClient client = new DeepgramClient(ClientOptions.builder() + .environment(Environment.PRODUCTION) + .webSocketFactory(factory) + .build()); + V1WebSocketClient webSocket = client.listen().v1().v1WebSocket(); + V1ConnectOptions options = + V1ConnectOptions.builder().model(ListenV1Model.NOVA3).build(); + + try { + webSocket.connect(options).get(5, TimeUnit.SECONDS); + assertThat(connectionAttempts).hasValue(1); + + client.close(); + + assertThat(closed.await(5, TimeUnit.SECONDS)) + .as("root close disconnects the active WebSocket") + .isTrue(); + assertThatThrownBy(() -> webSocket.connect(options)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("root client has been closed"); + assertThat(connectionAttempts) + .as("closed clients do not open another WebSocket") + .hasValue(1); + } finally { + client.close(); + } + } + @Test @DisplayName("closing a client does not release caller-owned HTTP resources") void doesNotCloseCustomClientResources() { diff --git a/src/test/java/com/deepgram/IntegrationTest.java b/src/test/java/com/deepgram/IntegrationTest.java index a6c2ef1c..251a37cb 100644 --- a/src/test/java/com/deepgram/IntegrationTest.java +++ b/src/test/java/com/deepgram/IntegrationTest.java @@ -14,9 +14,13 @@ import com.deepgram.resources.listen.v2.types.ListenV2TurnInfoEvent; import com.deepgram.resources.read.v1.text.requests.TextAnalyzeRequest; import com.deepgram.resources.speak.v1.audio.requests.SpeakV1Request; +import com.deepgram.resources.speak.v2.audio.requests.SpeakV2Request; +import com.deepgram.resources.speak.v2.audio.types.AudioGenerateRequestEncoding; import com.deepgram.resources.speak.v2.types.SpeakV2Close; import com.deepgram.resources.speak.v2.types.SpeakV2Flush; import com.deepgram.resources.speak.v2.types.SpeakV2Speak; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechMetadata; +import com.deepgram.resources.speak.v2.types.SpeakV2SpeechMetadataControlsApplied; import com.deepgram.resources.speak.v2.websocket.V2ConnectOptions; import com.deepgram.resources.speak.v2.websocket.V2WebSocketClient; import com.deepgram.types.ListProjectsV1Response; @@ -90,6 +94,14 @@ void setUp() { client = builder.build(); } + private void assumeFluxTtsControlsEnabled() { + // Controls are Early Access, so deployments opt in until production availability is universal. + String enabled = System.getenv("DEEPGRAM_FLUX_TTS_CONTROLS"); + org.junit.jupiter.api.Assumptions.assumeTrue( + enabled != null && !enabled.isEmpty(), + "DEEPGRAM_FLUX_TTS_CONTROLS not set, skipping Flux TTS Controls integration test"); + } + // --- Tier 1: Must pass before release --- @Nested @@ -330,6 +342,124 @@ void testIntegration_SpeakV2WebSocket() throws Exception { } } + @Test + @DisplayName("SpeakV2ControlsBatchPause - synthesize batch audio with a pause control") + void testIntegration_SpeakV2ControlsBatchPause() throws Exception { + assumeFluxTtsControlsEnabled(); + + SpeakV2Request request = SpeakV2Request.builder() + .model("flux-alexis-en") + .text("Before \\{pause:500ms\\} after.") + .encoding(AudioGenerateRequestEncoding.MP3) + .build(); + + try (InputStream audio = client.speak().v2().audio().generate(request)) { + assertThat(audio.readAllBytes()).as("expected audio bytes").isNotEmpty(); + } + } + + @Test + @DisplayName("SpeakV2ControlsBatchPronunciation - synthesize batch audio with a pronunciation control") + void testIntegration_SpeakV2ControlsBatchPronunciation() throws Exception { + assumeFluxTtsControlsEnabled(); + + SpeakV2Request request = SpeakV2Request.builder() + .model("flux-alexis-en") + .text("\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}") + .encoding(AudioGenerateRequestEncoding.MP3) + .build(); + + try (InputStream audio = client.speak().v2().audio().generate(request)) { + assertThat(audio.readAllBytes()).as("expected audio bytes").isNotEmpty(); + } + } + + @Test + @DisplayName("SpeakV2ControlsBatchConflict - reject speed with a pronunciation control") + void testIntegration_SpeakV2ControlsBatchConflict() { + assumeFluxTtsControlsEnabled(); + + SpeakV2Request request = SpeakV2Request.builder() + .model("flux-alexis-en") + .text("\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}") + .encoding(AudioGenerateRequestEncoding.MP3) + .speed(1.05) + .build(); + + assertThatThrownBy(() -> client.speak().v2().audio().generate(request)) + .isInstanceOf(DeepgramHttpException.class) + .satisfies(thrown -> { + DeepgramHttpException exception = (DeepgramHttpException) thrown; + assertThat(exception.statusCode()).isEqualTo(400); + assertThat(String.valueOf(exception.body())).contains("CONTROL_COMBINATION_INVALID"); + }); + } + + @Test + @DisplayName("SpeakV2ControlsWebSocket - stream audio and metadata with a pronunciation control") + void testIntegration_SpeakV2ControlsWebSocket() throws Exception { + assumeFluxTtsControlsEnabled(); + + V2WebSocketClient wsClient = client.speak().v2().v2WebSocket(); + CountDownLatch audioLatch = new CountDownLatch(1); + CountDownLatch metadataLatch = new CountDownLatch(1); + AtomicLong totalAudioBytes = new AtomicLong(0); + AtomicReference metadata = new AtomicReference<>(); + AtomicReference serverError = new AtomicReference<>(); + + wsClient.onSpeakV2Audio(audio -> { + totalAudioBytes.addAndGet(audio.size()); + audioLatch.countDown(); + }); + wsClient.onSpeechMetadata(value -> { + metadata.set(value); + metadataLatch.countDown(); + }); + wsClient.onErrorMessage(error -> serverError.set(String.valueOf(error))); + wsClient.onError(error -> serverError.set(error.getMessage())); + + try { + wsClient.connect(V2ConnectOptions.builder() + .model("flux-alexis-en") + .build()) + .get(15, TimeUnit.SECONDS); + wsClient.sendSpeak(SpeakV2Speak.builder() + .text("\\{\"word\": \"dupilumab\", \"pronounce\": \"duːˈpɪljuːmæb\"\\}") + .build()); + wsClient.sendFlush(SpeakV2Flush.builder().build()); + + assertThat(audioLatch.await(20, TimeUnit.SECONDS)) + .as("received audio") + .isTrue(); + assertThat(metadataLatch.await(20, TimeUnit.SECONDS)) + .as("received SpeechMetadata") + .isTrue(); + + assertThat(serverError.get()) + .as("no server/transport error during streaming") + .isNull(); + assertThat(totalAudioBytes.get()) + .as("expected streamed audio bytes") + .isGreaterThan(0); + + SpeakV2SpeechMetadata speechMetadata = metadata.get(); + assertThat(speechMetadata).as("received SpeechMetadata").isNotNull(); + SpeakV2SpeechMetadataControlsApplied controlsApplied = speechMetadata.getControlsApplied(); + assertThat(controlsApplied) + .as("received controls_applied metadata") + .isNotNull(); + assertThat(controlsApplied.getPronunciationsApplied()) + .as("the submitted pronunciation was applied") + .isEqualTo(1); + assertThat(controlsApplied.getBreaksApplied()).isZero(); + assertThat(controlsApplied.getPronunciationWarnings()).isZero(); + + wsClient.sendClose(SpeakV2Close.builder().build()); + } finally { + wsClient.disconnect(); + } + } + @Test @DisplayName("ListenV2ForceEndTurn - force a turn to end and read back trigger=manual") void testIntegration_ListenV2ForceEndTurn() throws Exception { diff --git a/src/test/java/com/deepgram/RegenTypesTest.java b/src/test/java/com/deepgram/RegenTypesTest.java index fffaed76..f2202c1a 100644 --- a/src/test/java/com/deepgram/RegenTypesTest.java +++ b/src/test/java/com/deepgram/RegenTypesTest.java @@ -30,6 +30,8 @@ import com.deepgram.types.ListenV1ResponseMetadata; import com.deepgram.types.ListenV1ResponseResultsChannelsItemAlternativesItemWordsItem; import com.deepgram.types.ListenV2Redact; +import com.deepgram.types.SharedIntents; +import com.deepgram.types.SharedTopics; import com.deepgram.types.ThinkSettingsV1FunctionsItem; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; @@ -376,4 +378,47 @@ void wordsItemSpeakerConfidenceAbsent() throws Exception { assertThat(word.getSpeaker()).isEmpty(); } } + + /** + * Coverage for the 2026-09-30 correction that removes the nonexistent nested {@code results} + * wrappers from Topics and Intents response objects. + */ + @Nested + @DisplayName("2026-09-30 Topics and Intents response shapes") + class TopicsAndIntentsShapes { + + @Test + @DisplayName("SharedTopics deserializes direct segments from the server payload") + void topicsDeserializeDirectSegments() throws Exception { + SharedTopics topics = MAPPER.readValue( + "{\"segments\":[{\"text\":\"Spacewalk\",\"topics\":[{\"topic\":\"Spacewalk\",\"confidence_score\":0.9}]}]}", + SharedTopics.class); + + assertThat(topics.getSegments()).hasValueSatisfying(segments -> { + assertThat(segments).singleElement().satisfies(segment -> { + assertThat(segment.getText()).contains("Spacewalk"); + assertThat(segment.getTopics()).hasValueSatisfying(values -> assertThat(values) + .singleElement() + .satisfies(value -> assertThat(value.getTopic()).contains("Spacewalk"))); + }); + }); + } + + @Test + @DisplayName("SharedIntents deserializes direct segments from the server payload") + void intentsDeserializeDirectSegments() throws Exception { + SharedIntents intents = MAPPER.readValue( + "{\"segments\":[{\"text\":\"Book a flight\",\"intents\":[{\"intent\":\"Book travel\",\"confidence_score\":0.9}]}]}", + SharedIntents.class); + + assertThat(intents.getSegments()).hasValueSatisfying(segments -> { + assertThat(segments).singleElement().satisfies(segment -> { + assertThat(segment.getText()).contains("Book a flight"); + assertThat(segment.getIntents()).hasValueSatisfying(values -> assertThat(values) + .singleElement() + .satisfies(value -> assertThat(value.getIntent()).contains("Book travel"))); + }); + }); + } + } } diff --git a/src/test/java/com/deepgram/StreamingAdditionalPropertiesWireTest.java b/src/test/java/com/deepgram/StreamingAdditionalPropertiesWireTest.java index 7ce1779c..a67a4ae8 100644 --- a/src/test/java/com/deepgram/StreamingAdditionalPropertiesWireTest.java +++ b/src/test/java/com/deepgram/StreamingAdditionalPropertiesWireTest.java @@ -6,6 +6,7 @@ import com.deepgram.types.ListenV1Model; import com.deepgram.types.ListenV2Model; import com.deepgram.types.SpeakV1Model; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import okhttp3.HttpUrl; @@ -71,6 +72,8 @@ private void assertEscapeHatchEmitted(HttpUrl url) { // The unmodeled params reached the wire, with a non-string value serialized correctly... assertThat(url.queryParameter("no_delay")).isEqualTo("true"); assertThat(url.queryParameter("custom_key")).isEqualTo("custom_value"); + assertThat(url.queryParameter("numeric_value")).isEqualTo("42"); + assertThat(url.queryParameterValues("multi_value")).containsExactly("first", "second"); // ...alongside (not instead of) the typed option. assertThat(url.queryParameterNames()).contains("model"); } @@ -83,9 +86,11 @@ void listenV1() throws Exception { .v1() .v1WebSocket() .connect(com.deepgram.resources.listen.v1.websocket.V1ConnectOptions.builder() - .model(ListenV1Model.NOVA3) - .additionalProperty("no_delay", true) - .additionalProperty("custom_key", "custom_value") + .model(ListenV1Model.NOVA3) + .additionalProperty("no_delay", true) + .additionalProperty("custom_key", "custom_value") + .additionalProperty("numeric_value", 42) + .additionalProperty("multi_value", List.of("first", "second")) .build()), "/v1/listen"); assertEscapeHatchEmitted(url); @@ -99,9 +104,11 @@ void listenV2() throws Exception { .v2() .v2WebSocket() .connect(com.deepgram.resources.listen.v2.websocket.V2ConnectOptions.builder() - .model(ListenV2Model.FLUX_GENERAL_EN) - .additionalProperty("no_delay", true) - .additionalProperty("custom_key", "custom_value") + .model(ListenV2Model.FLUX_GENERAL_EN) + .additionalProperty("no_delay", true) + .additionalProperty("custom_key", "custom_value") + .additionalProperty("numeric_value", 42) + .additionalProperty("multi_value", List.of("first", "second")) .build()), "/v2/listen"); assertEscapeHatchEmitted(url); @@ -115,9 +122,11 @@ void speakV1() throws Exception { .v1() .v1WebSocket() .connect(com.deepgram.resources.speak.v1.websocket.V1ConnectOptions.builder() - .model(SpeakV1Model.AURA2HERA_EN) - .additionalProperty("no_delay", true) - .additionalProperty("custom_key", "custom_value") + .model(SpeakV1Model.AURA2HERA_EN) + .additionalProperty("no_delay", true) + .additionalProperty("custom_key", "custom_value") + .additionalProperty("numeric_value", 42) + .additionalProperty("multi_value", List.of("first", "second")) .build()), "/v1/speak"); assertEscapeHatchEmitted(url); @@ -131,9 +140,11 @@ void speakV2() throws Exception { .v2() .v2WebSocket() .connect(com.deepgram.resources.speak.v2.websocket.V2ConnectOptions.builder() - .model("aura-2-thalia-en") - .additionalProperty("no_delay", true) - .additionalProperty("custom_key", "custom_value") + .model("aura-2-thalia-en") + .additionalProperty("no_delay", true) + .additionalProperty("custom_key", "custom_value") + .additionalProperty("numeric_value", 42) + .additionalProperty("multi_value", List.of("first", "second")) .build()), "/v2/speak"); assertEscapeHatchEmitted(url); diff --git a/src/test/java/com/deepgram/core/RetryInterceptorTest.java b/src/test/java/com/deepgram/core/RetryInterceptorTest.java index 70173211..25fc24ee 100644 --- a/src/test/java/com/deepgram/core/RetryInterceptorTest.java +++ b/src/test/java/com/deepgram/core/RetryInterceptorTest.java @@ -3,9 +3,16 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import okhttp3.MediaType; import okhttp3.OkHttpClient; +import okhttp3.Protocol; import okhttp3.Request; import okhttp3.Response; +import okhttp3.ResponseBody; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; @@ -37,6 +44,13 @@ private OkHttpClient buildClientWithRetries(int maxRetries) { .build(); } + private OkHttpClient buildClientWithRetriesAndCallTimeout(int maxRetries, long callTimeoutMillis) { + return new OkHttpClient.Builder() + .callTimeout(callTimeoutMillis, TimeUnit.MILLISECONDS) + .addInterceptor(new RetryInterceptor(maxRetries, Optional.of(0L), Optional.of(1000L), Optional.of(0.0))) + .build(); + } + private Request buildRequest() { return new Request.Builder().url(server.url("/test")).build(); } @@ -284,4 +298,60 @@ void testMultipleDifferentErrors() throws Exception { assertThat(server.getRequestCount()).isEqualTo(4); } } + + @Nested + @DisplayName("Retry transport failures") + class RetryTransportFailures { + + @Test + @DisplayName("returns the buffered retryable response when a later retry disconnects") + void preservesEarlierResponseAfterRetryTransportFailure() throws Exception { + AtomicInteger attempts = new AtomicInteger(); + client = new OkHttpClient.Builder() + .addInterceptor(new RetryInterceptor(1, Optional.of(0L), Optional.of(1000L), Optional.of(0.0))) + .addInterceptor(chain -> { + if (attempts.incrementAndGet() == 1) { + return new Response.Builder() + .request(chain.request()) + .protocol(Protocol.HTTP_1_1) + .code(500) + .message("original server error") + .header("X-Retry-Source", "original") + .body(ResponseBody.create( + "original server error".getBytes(StandardCharsets.UTF_8), + MediaType.get("text/plain"))) + .build(); + } + throw new IOException("retry transport failure"); + }) + .build(); + + Response response = client.newCall(buildRequest()).execute(); + + assertThat(response.code()).isEqualTo(500); + assertThat(response.header("X-Retry-Source")).isEqualTo("original"); + assertThat(response.body().string()).isEqualTo("original server error"); + assertThat(attempts).hasValue(2); + } + + @Test + @DisplayName("does not spend the next attempt timeout budget during Retry-After backoff") + void pausesCallTimeoutDuringRetryAfterBackoff() throws Exception { + client = buildClientWithRetriesAndCallTimeout(1, 100); + server.enqueue(new MockResponse() + .setResponseCode(429) + .setHeader("Retry-After", "1") + .setBody("rate limited")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("success")); + + long start = System.nanoTime(); + Response response = client.newCall(buildRequest()).execute(); + long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + + assertThat(response.code()).isEqualTo(200); + assertThat(response.body().string()).isEqualTo("success"); + assertThat(server.getRequestCount()).isEqualTo(2); + assertThat(elapsedMillis).isGreaterThanOrEqualTo(900L); + } + } }