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": "3063c73a3a1dabef36c44211e69ea5e5c1f2b8f0",
"originGitCommit": "3dc7e52769409adb2a41e006ee1fc80fdf642a20",
"originGitCommitIsDirty": true,
"invokedBy": "manual",
"sdkVersion": "0.11.2"
Expand Down
8 changes: 8 additions & 0 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,19 @@ 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

# Agent V1 forwards raw frames through onMessage(String); suppress generic errors only when a raw
# handler is registered, otherwise retain legacy onError delivery for unknown control frames.
src/main/java/com/deepgram/resources/agent/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

# Preserve existing AgentV1ConversationText builder implementations after adding conversationalBehavior.
src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ConversationText.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 Expand Up @@ -93,6 +100,7 @@ src/main/java/com/deepgram/resources/agent/v1/types/AgentV1KeepAlive.java
src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkUpdated.java
src/main/java/com/deepgram/resources/agent/v1/types/AgentV1PromptUpdated.java
src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ForceEndTurn.java
src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkLiveUpdated.java

# Union default-variant fix (STOPGAP). The agent listen-provider unions declare `version` as an
# optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits). Fern
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ How to identify:

Current temporarily frozen files:

- `src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java` - Agent V1 already delivers every raw control frame through `onMessage(String)`. Suppress generic errors for unknown frames only when a raw handler is registered; otherwise retain legacy `onError` delivery. Preserve the generated Think Live, dynamic-injection, and frustration handlers during reconciliation.
- `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ConversationText.java` - keeps newly added `conversationalBehavior(...)` methods on the public `_FinalStage` builder interface as default no-ops. Generated concrete builders override them normally; the defaults preserve source and binary compatibility for third-party implementations compiled before Think Live existed.
- `src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkLiveUpdated.java` - fields-less event type with a manual `hashCode()` patch. Fern generates `equals()` but no matching `hashCode()`, violating the Object contract; retain the patch, freeze entry, and `RegenTypesTest.FieldsLessMessageContract` assertion until Fern fixes the pattern.
- `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. 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.
Expand Down
99 changes: 99 additions & 0 deletions examples/agent/ThinkLiveAgent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import com.deepgram.DeepgramClient;
import com.deepgram.resources.agent.v1.types.AgentV1InjectDynamicAgentMessage;
import com.deepgram.resources.agent.v1.types.AgentV1InjectDynamicAgentMessageBehavior;
import com.deepgram.resources.agent.v1.types.AgentV1Settings;
import com.deepgram.resources.agent.v1.types.AgentV1SettingsAgent;
import com.deepgram.resources.agent.v1.types.AgentV1SettingsAgentContext;
import com.deepgram.resources.agent.v1.types.AgentV1SettingsAgentContextThink;
import com.deepgram.resources.agent.v1.types.AgentV1SettingsAudio;
import com.deepgram.resources.agent.v1.types.AgentV1UpdateThinkLive;
import com.deepgram.resources.agent.v1.websocket.V1WebSocketClient;
import com.deepgram.types.DeepgramThinkLiveProviderV1;
import com.deepgram.types.OpenAiThinkProvider;
import com.deepgram.types.OpenAiThinkProviderModel;
import com.deepgram.types.ThinkLiveSettingsV1;
import com.deepgram.types.ThinkSettingsV1;
import com.deepgram.types.ThinkSettingsV1Provider;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
* Configures an Agent V1 session with Think Live, updates its live behavior, and injects a dynamic agent message.
*
* <p>Usage: {@code java ThinkLiveAgent}
*/
public class ThinkLiveAgent {
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);
}

DeepgramClient client = DeepgramClient.builder().apiKey(apiKey).build();
V1WebSocketClient wsClient = client.agent().v1().v1WebSocket();
CountDownLatch closeLatch = new CountDownLatch(1);

try {
wsClient.onWelcome(welcome -> wsClient.sendSettings(initialSettings()));
wsClient.onSettingsApplied(event -> {
System.out.println("Think Live settings applied; updating backchannel behavior.");
wsClient.sendUpdateThinkLive(AgentV1UpdateThinkLive.builder()
.thinkLive(thinkLiveSettings(0.65))
.build());
});
wsClient.onThinkLiveUpdated(event -> {
System.out.println("Think Live configuration updated; requesting a dynamic response.");
wsClient.sendInjectDynamicAgentMessage(AgentV1InjectDynamicAgentMessage.builder()
.behavior(AgentV1InjectDynamicAgentMessageBehavior.QUEUE)
.build());
});
wsClient.onConversationText(text -> System.out.printf(
"[%s, %s] %s%n",
text.getRole(),
text.getConversationalBehavior().map(Object::toString).orElse("default"),
text.getContent()));
wsClient.onFrustrationDetected(event -> System.out.printf(
"Frustration detected: count=%d, escalated=%s%n", event.getCount(), event.getEscalated()));
wsClient.onInjectionRefused(event -> System.out.println("Dynamic injection refused: " + event));
wsClient.onErrorMessage(event -> System.err.println("Agent error: " + event));
wsClient.onError(error -> System.err.println("WebSocket error: " + error.getMessage()));
wsClient.onDisconnected(reason -> closeLatch.countDown());

wsClient.connect().get(10, TimeUnit.SECONDS);
closeLatch.await(60, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
} finally {
wsClient.disconnect();
}
}

private static AgentV1Settings initialSettings() {
OpenAiThinkProvider provider = OpenAiThinkProvider.builder()
.model(OpenAiThinkProviderModel.GPT4O_MINI)
.build();
return AgentV1Settings.builder()
.audio(AgentV1SettingsAudio.builder().build())
.agent(AgentV1SettingsAgent.of(AgentV1SettingsAgentContext.builder()
.think(AgentV1SettingsAgentContextThink.of(ThinkSettingsV1.builder()
.provider(ThinkSettingsV1Provider.openAi(provider))
.prompt("You are a concise, helpful voice assistant.")
.build()))
.thinkLive(thinkLiveSettings(0.35))
.greeting("Hello. I can respond while you are speaking.")
.build()))
.build();
}

private static ThinkLiveSettingsV1 thinkLiveSettings(double backchannelSensitivity) {
return ThinkLiveSettingsV1.builder()
.provider(DeepgramThinkLiveProviderV1.builder()
.agentBackchannelSensitivity(backchannelSensitivity)
.presenceCheckMaxCount(2)
.presenceCheckIntervalMs(10_000)
.frustrationMaxCount(3)
.build())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public final class AgentV1ConversationText {

private final String content;

private final Optional<AgentV1ConversationTextConversationalBehavior> conversationalBehavior;

private final Optional<List<String>> languagesHinted;

private final Optional<List<String>> languages;
Expand All @@ -35,11 +37,13 @@ public final class AgentV1ConversationText {
private AgentV1ConversationText(
AgentV1ConversationTextRole role,
String content,
Optional<AgentV1ConversationTextConversationalBehavior> conversationalBehavior,
Optional<List<String>> languagesHinted,
Optional<List<String>> languages,
Map<String, Object> additionalProperties) {
this.role = role;
this.content = content;
this.conversationalBehavior = conversationalBehavior;
this.languagesHinted = languagesHinted;
this.languages = languages;
this.additionalProperties = additionalProperties;
Expand Down Expand Up @@ -69,6 +73,14 @@ public String getContent() {
return content;
}

/**
* @return Annotates what kind of utterance the message represents: <code>default</code> (a standard turn), <code>backchannel</code> (a short acknowledgement while the user held the floor), <code>presence_check</code> (a prompt after extended silence), or <code>filler</code> (a line covering a wait on a function call or a dynamic InjectAgentMessage). Always sent; it is <code>default</code> for every message while Think Live is off
*/
@JsonProperty("conversational_behavior")
public Optional<AgentV1ConversationTextConversationalBehavior> getConversationalBehavior() {
return conversationalBehavior;
}

/**
* @return The language hints that were active at the time of the turn. Only present on user-role messages when the listen model is flux-general-multi.
*/
Expand Down Expand Up @@ -99,13 +111,14 @@ public Map<String, Object> getAdditionalProperties() {
private boolean equalTo(AgentV1ConversationText other) {
return role.equals(other.role)
&& content.equals(other.content)
&& conversationalBehavior.equals(other.conversationalBehavior)
&& languagesHinted.equals(other.languagesHinted)
&& languages.equals(other.languages);
}

@java.lang.Override
public int hashCode() {
return Objects.hash(this.role, this.content, this.languagesHinted, this.languages);
return Objects.hash(this.role, this.content, this.conversationalBehavior, this.languagesHinted, this.languages);
}

@java.lang.Override
Expand Down Expand Up @@ -140,6 +153,18 @@ public interface _FinalStage {

_FinalStage additionalProperties(Map<String, Object> additionalProperties);

/**
* <p>Annotates what kind of utterance the message represents: <code>default</code> (a standard turn), <code>backchannel</code> (a short acknowledgement while the user held the floor), <code>presence_check</code> (a prompt after extended silence), or <code>filler</code> (a line covering a wait on a function call or a dynamic InjectAgentMessage). Always sent; it is <code>default</code> for every message while Think Live is off</p>
*/
default _FinalStage conversationalBehavior(
Optional<AgentV1ConversationTextConversationalBehavior> conversationalBehavior) {
return this;
}

default _FinalStage conversationalBehavior(AgentV1ConversationTextConversationalBehavior conversationalBehavior) {
return this;
}

/**
* <p>The language hints that were active at the time of the turn. Only present on user-role messages when the listen model is flux-general-multi.</p>
*/
Expand All @@ -165,6 +190,8 @@ public static final class Builder implements RoleStage, ContentStage, _FinalStag

private Optional<List<String>> languagesHinted = Optional.empty();

private Optional<AgentV1ConversationTextConversationalBehavior> conversationalBehavior = Optional.empty();

@JsonAnySetter
private Map<String, Object> additionalProperties = new HashMap<>();

Expand All @@ -174,6 +201,7 @@ private Builder() {}
public Builder from(AgentV1ConversationText other) {
role(other.getRole());
content(other.getContent());
conversationalBehavior(other.getConversationalBehavior());
languagesHinted(other.getLanguagesHinted());
languages(other.getLanguages());
return this;
Expand Down Expand Up @@ -241,9 +269,32 @@ public _FinalStage languagesHinted(Optional<List<String>> languagesHinted) {
return this;
}

/**
* <p>Annotates what kind of utterance the message represents: <code>default</code> (a standard turn), <code>backchannel</code> (a short acknowledgement while the user held the floor), <code>presence_check</code> (a prompt after extended silence), or <code>filler</code> (a line covering a wait on a function call or a dynamic InjectAgentMessage). Always sent; it is <code>default</code> for every message while Think Live is off</p>
* @return Reference to {@code this} so that method calls can be chained together.
*/
@java.lang.Override
public _FinalStage conversationalBehavior(
AgentV1ConversationTextConversationalBehavior conversationalBehavior) {
this.conversationalBehavior = Optional.ofNullable(conversationalBehavior);
return this;
}

/**
* <p>Annotates what kind of utterance the message represents: <code>default</code> (a standard turn), <code>backchannel</code> (a short acknowledgement while the user held the floor), <code>presence_check</code> (a prompt after extended silence), or <code>filler</code> (a line covering a wait on a function call or a dynamic InjectAgentMessage). Always sent; it is <code>default</code> for every message while Think Live is off</p>
*/
@java.lang.Override
@JsonSetter(value = "conversational_behavior", nulls = Nulls.SKIP)
public _FinalStage conversationalBehavior(
Optional<AgentV1ConversationTextConversationalBehavior> conversationalBehavior) {
this.conversationalBehavior = conversationalBehavior;
return this;
}

@java.lang.Override
public AgentV1ConversationText build() {
return new AgentV1ConversationText(role, content, languagesHinted, languages, additionalProperties);
return new AgentV1ConversationText(
role, content, conversationalBehavior, languagesHinted, languages, additionalProperties);
}

@java.lang.Override
Expand Down
Loading
Loading