Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .fern/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"enable-wire-tests": true,
"runtime-version": true
},
"originGitCommit": "59a99c27e5f91158072fc98b3567fc88c3315f67",
"originGitCommit": "3063c73a3a1dabef36c44211e69ea5e5c1f2b8f0",
"originGitCommitIsDirty": true,
"invokedBy": "manual",
"sdkVersion": "0.11.1"
Expand Down
7 changes: 7 additions & 0 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ src/main/java/com/deepgram/core/transport/
# (thrown by the frozen listen v1 V1WebSocketClient below when no onErrorMessage handler is set).
# No Fern equivalent; the generator would delete it.
src/main/java/com/deepgram/resources/listen/v1/websocket/ListenV1ErrorException.java
src/main/java/com/deepgram/resources/listen/v1/websocket/ListenV1ConfigureFailureException.java

# Bug fixes for maxRetries(0) semantics ("connect once, don't retry") and a
# configurable connectionTimeoutMs on ReconnectOptions (was hardcoded 4000ms).
Expand Down Expand Up @@ -57,6 +58,12 @@ 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

# Source and binary compatibility for third-party implementations of generated builder stages.
# Generated concrete builders override languageHint; default interface methods preserve existing custom implementations.
src/main/java/com/deepgram/resources/listen/v1/websocket/V1ConnectOptions.java
src/main/java/com/deepgram/resources/listen/v1/media/requests/ListenV1RequestUrl.java
src/main/java/com/deepgram/resources/listen/v1/media/requests/MediaTranscribeRequestOctetStream.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
Expand Down
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Current permanently frozen files:

- `src/main/java/com/deepgram/DeepgramClient.java`, `src/main/java/com/deepgram/AsyncDeepgramClient.java`, `src/main/java/com/deepgram/DeepgramClientBuilder.java`, `src/main/java/com/deepgram/AsyncDeepgramClientBuilder.java` - custom wrapper entrypoints that add Bearer auth, session ID support, and custom transport behavior on top of Fern's generated API client
- `src/main/java/com/deepgram/core/transport/` - hand-written transport abstraction
- `src/main/java/com/deepgram/resources/listen/v1/websocket/ListenV1ErrorException.java` - hand-written exception carrying a Listen V1 server `Error` (`ListenV1Error`) to the generic `onError` handler when no `onErrorMessage` handler is registered; thrown by the frozen listen v1 `V1WebSocketClient`. No Fern equivalent
- `src/main/java/com/deepgram/resources/listen/v1/websocket/ListenV1ErrorException.java` and `src/main/java/com/deepgram/resources/listen/v1/websocket/ListenV1ConfigureFailureException.java` - hand-written exceptions carrying Listen V1 server `Error` and `ConfigureFailure` frames to the generic `onError` handler when no typed handler is registered; thrown by the frozen listen v1 `V1WebSocketClient`. No Fern equivalent
- `build.gradle`, `settings.gradle`, `gradle/`, `gradlew`, `gradlew.bat`, `mvnw`, `mvnw.cmd`, `.mvn/`, `pom.xml`, `Makefile` - build and project configuration
- `README.md`, `CHANGELOG.md`, `CONTRIBUTING.md`, `LICENSE`, `docs/` - docs
- `src/test/` - manually maintained tests
Expand All @@ -50,8 +50,9 @@ Current temporarily frozen files:

- `src/main/java/com/deepgram/core/ClientOptions.java` - preserves release-please version markers, correct SDK header constants, and the accurate `close()` lifecycle Javadoc 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 `./mvnw 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/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. Listen V2 additionally tracks a successfully sent `CloseStream` and treats a following no-status (`1005`) close as terminal; queued or rejected controls still reconnect, and `src/test/java/com/deepgram/ListenV2ServerCloseTest.java` guards all three paths. Use the standard `.bak` swap/restore workflow during regen review; re-apply the no-op and terminal-close safeguards after regen.
- `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. Listen V1 also forwards a parsed server `Error` to `onError` as a `ListenV1ErrorException` (carrying the typed `ListenV1Error`) when no `onErrorMessage` handler is registered, preserving the prior generic error-handler behavior. 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).
- `src/main/java/com/deepgram/resources/listen/v1/websocket/V1ConnectOptions.java`, `src/main/java/com/deepgram/resources/listen/v1/media/requests/ListenV1RequestUrl.java`, and `src/main/java/com/deepgram/resources/listen/v1/media/requests/MediaTranscribeRequestOctetStream.java` - keep newly added `languageHint(...)` methods on public `_FinalStage` builder interfaces as default no-ops. Generated concrete builders override them normally; the defaults preserve source and binary compatibility for third-party implementations compiled before the optional setting existed. Re-apply after regen until Fern emits compatible defaults.
- 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. Renee is an unlisted voice: the spec owner removed it from the catalog on purpose (#project-flux-tts, 2026-08-19), but it still synthesizes (`POST /v2/speak?model=flux-renee-en` returns 200 with valid audio), so dropping the constant would be a source break for 0.8.0+ callers over a catalog delisting alone. The constant and its `Visitor` method are marked `@Deprecated` with a note pointing callers to a listed voice. Five touchpoints: the constant, the `Value` enum entry, the `visit()` case, the `valueOf()` case, and the `Visitor` method (the constant and `Visitor` method carry the `@Deprecated` tag and Javadoc). **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. The spec will not list the voice again, so drop the Renee touchpoints at the next major release; keep the file frozen until then for the reasons above.
- 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).
Expand Down
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ ws.onErrorMessage(error -> {
System.err.println("Server error: " + error.getVariant() + ": " + error.getDescription());
});

ws.onConfigureSuccess(success -> {
System.out.println("Configuration applied: " + success.getRequestId());
});

ws.onConfigureFailure(failure -> {
System.err.println("Configuration rejected: " + failure.getCode() + ": " + failure.getDescription());
});

// Connect with options (model is required)
ws.connect(V1ConnectOptions.builder()
.model(ListenV1Model.NOVA3)
Expand All @@ -303,7 +311,7 @@ ws.connect(V1ConnectOptions.builder()

// Update Nova-3 keyterms and numerals without reconnecting. Keep the keyterm list under the
// 500-token limit: an over-limit update does not close the stream, and the server may reply
// with a ConfigureFailure (CONFIGURE_KEYTERM_LIMIT_EXCEEDED), which this client reports to onError.
// with a typed ConfigureFailure (CONFIGURE_KEYTERM_LIMIT_EXCEEDED).
ws.sendConfigure(ListenV1Configure.builder()
.keyterms(List.of("Deepgram"))
.features(Map.of("numerals", true))
Expand All @@ -320,6 +328,56 @@ ws.sendCloseStream(ListenV1CloseStream.builder()
ws.close();
```

### Nova-3 multilingual language hints

With `model=nova-3` and `language=multi`, `language_hint` biases language detection toward the languages you expect; it does not restrict the transcript to them, and it is ignored with any other `language`. Each alternative carries the detected `languages` and the normalized `languages_hinted` (an empty list when no hint was sent). A code outside the Nova-3 multilingual list fails the request with HTTP 400.

```java
import com.deepgram.resources.listen.v1.media.requests.ListenV1RequestUrl;
import com.deepgram.resources.listen.v1.media.types.MediaTranscribeRequestModel;
import com.deepgram.resources.listen.v1.media.types.MediaTranscribeResponse;
import com.deepgram.resources.listen.v1.websocket.V1ConnectOptions;
import com.deepgram.resources.listen.v1.websocket.V1WebSocketClient;
import com.deepgram.types.ListenV1AcceptedResponse;
import com.deepgram.types.ListenV1Language;
import com.deepgram.types.ListenV1LanguageHint;
import com.deepgram.types.ListenV1Model;
import com.deepgram.types.ListenV1Response;

// Pre-recorded (MediaTranscribeResponse is a union type; see "Access transcription" above)
MediaTranscribeResponse result = client.listen().v1().media().transcribeUrl(ListenV1RequestUrl.builder()
.url("https://dpgr.am/spacewalk.wav")
.model(MediaTranscribeRequestModel.NOVA3)
.language("multi")
.languageHint(List.of("en", "es"))
.build());
result.visit(new MediaTranscribeResponse.Visitor<Void>() {
@Override
public Void visit(ListenV1Response response) {
response.getResults().getChannels().get(0).getAlternatives().ifPresent(alts -> {
alts.get(0).getLanguagesHinted().ifPresent(hinted -> System.out.println("Hints applied: " + hinted));
alts.get(0).getLanguages().ifPresent(detected -> System.out.println("Detected: " + detected));
});
return null;
}

@Override
public Void visit(ListenV1AcceptedResponse accepted) {
return null;
}

});

// Streaming
V1WebSocketClient ws = client.listen().v1().v1WebSocket();
ws.connect(V1ConnectOptions.builder()
.model(ListenV1Model.NOVA3)
.language(ListenV1Language.of("multi"))
.languageHint(ListenV1LanguageHint.of(List.of("en", "es")))
.build())
.get(10, TimeUnit.SECONDS);
```

### Flux Transcription (Listen V2 WebSocket)

Flux supports turn-aware streaming transcription. Set `numerals` when connecting, or change it for turns transcribed
Expand Down
16 changes: 14 additions & 2 deletions examples/listen/LiveReconfigure.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
* Reconfigures an active Nova-3 Listen V1 stream without reconnecting.
*
* <p>Keep the keyterm list under the 500-token limit: an over-limit update does not close the stream, and the server
* may reply with a {@code ConfigureFailure} ({@code CONFIGURE_KEYTERM_LIMIT_EXCEEDED}), which this client reports to
* {@code onError}.
* may reply with a typed {@code ConfigureFailure} ({@code CONFIGURE_KEYTERM_LIMIT_EXCEEDED}).
*
* <p>Usage: {@code DEEPGRAM_API_KEY=... java LiveReconfigure}
*/
Expand All @@ -32,6 +31,7 @@ public static void main(String[] args) {
DeepgramClient client = DeepgramClient.builder().apiKey(apiKey).build();
V1WebSocketClient wsClient = client.listen().v1().v1WebSocket();
CountDownLatch closeLatch = new CountDownLatch(1);
CountDownLatch configureLatch = new CountDownLatch(1);

try {
wsClient.onConnected(() -> System.out.println("Connected to Deepgram"));
Expand All @@ -48,6 +48,15 @@ public static void main(String[] args) {
}
});
wsClient.onErrorMessage(LiveReconfigure::printListenError);
wsClient.onConfigureSuccess(success -> {
System.out.println("Configuration applied: request_id=" + success.getRequestId());
success.getKeyterms().ifPresent(keyterms -> System.out.println("Active keyterms: " + keyterms));
configureLatch.countDown();
});
wsClient.onConfigureFailure(failure -> {
System.err.println("Configuration rejected (" + failure.getCode() + "): " + failure.getDescription());
configureLatch.countDown();
});
wsClient.onError(error -> System.err.println("WebSocket error occurred: " + error.getMessage()));
wsClient.onDisconnected(reason -> {
System.out.println("Connection closed.");
Expand All @@ -65,6 +74,9 @@ public static void main(String[] args) {
.build())
.get(10, TimeUnit.SECONDS);
System.out.println("Sent live reconfiguration for keyterms and numerals.");
if (!configureLatch.await(10, TimeUnit.SECONDS)) {
throw new IllegalStateException("Timed out waiting for ConfigureSuccess or ConfigureFailure");
}

// Stream audio after this point, for example: wsClient.sendMedia(audioChunk);
wsClient.sendCloseStream(ListenV1CloseStream.builder()
Expand Down
Loading
Loading