feat(regen)!: listen v2 force-end-turn + diarize metadata; rename provider/agent-history types - #96
Open
GregHolmes wants to merge 12 commits into
Open
feat(regen)!: listen v2 force-end-turn + diarize metadata; rename provider/agent-history types#96GregHolmes wants to merge 12 commits into
GregHolmes wants to merge 12 commits into
Conversation
GregHolmes
requested review from
deepgram-kiley and
dg-coreylweathers
as code owners
August 19, 2026 09:13
Fern generator 4.16.0 -> 4.18.0 (CLI 5.44.6 -> 5.95.1).
All five patch groups were re-applied verbatim: 4.18.0 fixed none of them.
- core/ClientOptions: restored the hardcoded SDK version literals and their
// x-release-please-version markers. 4.18.0 switched to a getSdkVersion()
helper reading Package.getImplementationVersion(), but build.gradle sets no
jar manifest attributes, so that always resolves null and the SDK would
permanently report the hardcoded "0.8.1" fallback. It also changed User-Agent
from "com.deepgram:" to "com.deepgram." while leaving X-Fern-SDK-Name on the
colon form, so the two headers disagreed.
- core/ReconnectingWebSocketListener: restored maxRetries(0) semantics
("connect once, don't retry"), the configurable connectionTimeoutMs, and
applyOptionsOverride(). The last is load-bearing: core/transport/
TransportWebSocketFactory (permanently frozen) calls it, so main source does
not compile without it.
- listen v1/v2 + speak v1/v2 WebSocket clients: restored multi-value query-param
serialization via QueryStringMapper(arraysAsRepeats=true) and the
additionalProperties escape hatch on connect().
- listen v2 + speak v2 WebSocket clients: restored the forward-compat no-op for
unrecognized message types.
- 11 fields-less message types: restored the manual hashCode() patch.
- 3 agent listen-provider unions: restored defaultImpl = V2Value.
Generator changes carried forward, not reverted:
- listen v2 gained sendForceEndTurn(ListenV2ForceEndTurn), grafted onto the
patched client.
- New types: ListenV2ForceEndTurn, ListenV2TurnInfo, SpeakV2Speed,
ListenV1ResultsMetadataDiarizeInfo, ListenV1ResponseMetadataDiarizeInfo.
Breaking generator changes requiring hand-maintained call-site updates:
- 9 provider types lost their context prefixes (DeepgramSpeakProviderModel ->
DeepgramModel, AnthropicThinkProviderModel -> AnthropicModel,
GoogleThinkProviderModel/Version -> GoogleModel/GoogleVersion,
CartesiaSpeakProviderModelId/Voice -> CartesiaModelId/CartesiaVoice,
GroqThinkProviderReasoningMode -> GroqReasoningMode, AgentV1History* ->
ConversationHistoryMessage*). Updated RegenTypesTest and the two agent
examples.
- speak v2 "speed" changed from a free double to the closed SpeakV2Speed enum
(7 values, 0.85-1.15, serialized as a string). Updated SpeakV2ConnectWireTest.
.fernignore is back to its pre-regen state (all 20 paths restored, no .bak
entries) and every .bak file is deleted.
Verified: ./gradlew test compileExamples -> 112 tests, 0 failures, 1 skipped
(pre-existing).
…nale Two gaps in the preceding regen-reconciliation commit. 1. ListenV2ForceEndTurn was generated unpatched. It is a 12th fields-less message type with equals() and no hashCode(), so it violated the Object contract like the other 11. Diffing .bak files could not catch it: the type is newly generated (reverted out in #91, reintroduced by 4.18.0), so it had no .bak to compare against. Added the hashCode() patch, froze it in .fernignore, listed it in AGENTS.md, and extended RegenTypesTest.FieldsLessMessageContract to assert it. Added a post-regen sweep step to AGENTS.md so a newly generated fields-less type cannot slip through this way again. 2. The ClientOptions rationale in the previous commit message and the PR body was wrong on a point of fact. It claimed 4.18.0's getSdkVersion() helper "always resolves null" because build.gradle sets no jar manifest attributes. That is only true for Gradle builds and tests. The published artifact is built by Maven (release-please.yml runs `mvn deploy -P release`) and pom.xml's maven-jar-plugin sets addDefaultImplementationEntries=true, so the published JAR manifest does carry Implementation-Version and the generator's helper would resolve correctly there. The decision to keep the explicit literals still stands, on the accurate rationale: they are correct under Gradle, Maven, and tests alike, whereas getSdkVersion() is correct only via Maven and silently reports a stale generator-authored fallback everywhere else. release-please already lists this file in extra-files, so the markers are actively wired. Recorded in AGENTS.md. Verified: ./gradlew test compileExamples -> 112 tests, 0 failures, 1 skipped.
…etype Generator 4.18.0 retyped V2ConnectOptions.speed from Optional<Double> to the SpeakV2Speed string-literal enum. The preceding commit accepted that and updated SpeakV2ConnectWireTest to match, which was wrong — it shipped a breaking change to a parameter that went out as numeric in 0.8.0, and papered over it by editing the test rather than questioning the type. Two things establish the generated type is wrong, not merely inconvenient: - The spec contradicts itself. The sibling mid-stream SpeakV2Configure.speed is still Optional<Double>, so the same value is numeric on one message and a string enum on the other. - The server parses the numeric form and range-checks it, reporting SPEED_OUT_OF_RANGE and SPEED_INCREMENT_INVALID. A closed 7-value enum cannot express a valid in-range value it happens to omit. Restored speed to Optional<Double>, froze V2ConnectOptions in .fernignore, and reverted SpeakV2ConnectWireTest to `.speed(1.05)` so it once again asserts the same behaviour it did on main. The generated SpeakV2Speed type is left in place, unused, as documentation of the accepted values. Verified: ./gradlew test compileExamples -> 112 tests, 0 failures, 1 skipped.
Documents the breaking changes rather than shimming them, matching the convention this repo used for 0.8.0: the renames ship, and the migration path is written down. Covers the nine provider/agent-history type renames, the two removed duplicate agent conversation-history leaf types, and the removal of DeepgramModel.FLUX_RENEE_EN. Every change is source-level with identical wire values, so the guide says so explicitly — a clean recompile means the upgrade is done and no request payloads change. Also documents what deliberately did NOT change: V2ConnectOptions.speed stays Optional<Double> despite the generator retyping it, so readers who see the generated SpeakV2Speed type do not assume their speed calls need updating. Additive coverage: sendForceEndTurn (flagged as requiring server-side enablement and not yet generally available), ListenV2TurnInfo.getTrigger(), the Listen V1 diarize_info metadata and per-word speaker_confidence, and the 25 new DeepgramModel voice constants. Linked from the README migration index as current.
Generator 4.18.0 removed this constant. The preceding migration guide documented that as a retirement and told readers to "pick another Flux voice" — that was wrong, and verifying against the live API showed why. The voice is fully functional: - POST /v2/speak?model=flux-renee-en returns 200 with a valid 3.1s MP3 (MPEG layer III, 48 kbps, 24 kHz mono — Flux TTS's output format). - The name resolves in the server's model registry, not by prefix: an invented flux-bogus-nonexistent-en is rejected with INVALID_QUERY_PARAMETER, while flux-renee-en is accepted. - Internally it appears in a July 2026 full regression sweep across all speakers and in current voice rosters, with no deprecation or sunset language. So the removal is a spec regression, not a retirement, and dropping the constant would break 0.8.0 callers for nothing. Restored all five touchpoints (the constant, the Value enum entry, the visit() case, the valueOf() case, and the Visitor method) and froze DeepgramModel in .fernignore. Guarded by a new RegenTypesTest case asserting the wire value, valueOf() resolution, and JSON round-trip. The 25 constants 4.18.0 added are all retained: the enum now has 100, with nothing removed relative to 0.8.0. Freezing this file carries a risk the other frozen files do not — it receives frequent additive spec changes, so restoring its .bak wholesale on a future regen would silently drop new voices. Both .fernignore and AGENTS.md carry an explicit warning to diff and carry additions forward instead. Migration guide updated: FLUX_RENEE_EN moves out of the breaking-changes list into the "unchanged despite generator churn" section alongside the speed restoration, leaving two documented breaking changes. Verified: ./gradlew test compileExamples -> 113 tests, 0 failures, 1 skipped.
An API surface diff against 0.8.0 (javap over all 1688 public types) showed the renames ripple further than the type list implies: 30 public members are removed from 21 surviving types, because seven carrier types changed both their getter return types and their builder parameter types. The guide previously made this point only for Google.getVersion(). Now lists all seven carriers with their affected members, and distinguishes the inline-constant case (import change is enough) from stored values (declarations need updating). The same diff confirmed the scope is bounded: all 53 removed public types and all 30 removed members trace to the 11 renamed/deleted types, with zero unexplained removals and no new required builder stages.
The provider renames shortened type names enough that two builder chains in examples/agent/ProviderCombinations.java now fit on one line, so the formatter wanted them unwrapped. Applied ./gradlew spotlessApply -- cosmetic only. Also corrects the documented post-regen validation command in AGENTS.md to ./gradlew spotlessCheck test compileExamples. CI runs Spotless Check as its own job, so the previously documented 'test compileExamples' passed locally while CI failed, which is exactly what happened here. Re-applying patches by hand tends to trip it, since inserted comments and renamed types change line lengths and hence how chained calls wrap.
Closes the coverage gaps found while auditing the branch. 113 -> 123 tests. ListenV2ControlFrameWireTest (new): sendForceEndTurn had no test at all. The server gates the message on every deployment we can reach, so an end-to-end test is impossible -- but that does not block asserting the half we own, that the client puts a correctly serialized frame on the wire. Three cases: the frame reaches the server, it carries nothing but the discriminator (asserted with an exact match, since the server rejects unknown fields on control messages), and sendCloseStream works as a control so a future failure distinguishes "the send path broke" from "ForceEndTurn broke". RegenTypesTest.Regen20260819 (new nested class): the read-side fields this regen added had no coverage, and being additive, a later regen could drop or rename one without any test noticing. Seven cases across ListenV2TurnInfo.trigger, both DiarizeInfo variants (listen.v1.types and types), and words-item speaker_confidence -- each asserted from a realistic server payload rather than a builder round-trip, so they exercise the server-to-SDK direction. Every field is also asserted in the absent case, which is what deployed clients hit today: trigger is not emitted until the force-end-turn gate lifts, diarize_info only appears when diarization is on, and speaker_confidence is pre-recorded only. IntegrationTest: the SpeakV2WebSocket test now sets speed(1.05). The speed patch was previously guarded only by a mock-server wire test, so nothing exercised it against a real server. Verified passing against the live API; a regression to the generator's string form would surface as a server error in that test. Verified: ./gradlew spotlessCheck test compileExamples -> 123 tests, 0 failures, 1 skipped (SpeakV2WebSocket, opt-in via DEEPGRAM_SPEAK_V2_WS). With that flag set, integrationTest is 7/7.
The ForceEndTurn gate has opened on staging, so the feature is now verifiable end-to-end rather than only at the wire level. Add an opt-in integration test (DEEPGRAM_LISTEN_V2_FORCE_END_TURN=1) that streams real audio, waits for StartOfTurn, forces the turn to end, and asserts trigger=manual with no server error. It skips cleanly where the feature is gated, matching the existing DEEPGRAM_SPEAK_V2_WS pattern. This is the first live coverage of the listen v2 WebSocket in this repo. Add examples/listen/ForceEndTurn.java, the first example that actually streams audio to listen v2 and so the first to reach a TurnInfo. It reports and exits cleanly on a deployment without the feature. Correct the claims that the gate was closed everywhere. The earlier evidence rested on trigger being absent from every TurnInfo, but trigger only appears on EndOfTurn, which a gated deployment never reaches once it closes the socket. Streaming through to a natural EndOfTurn is what actually shows the field is not emitted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fern SDK regeneration for 2026-08-19 (
fern-java-sdk4.16.0 → 4.18.0, CLI 5.44.6 → 5.95.1, spec03f0677→068de88).Status: reconciled & verified. All 8 CI checks green — Spotless, Compile (Java 11/17/21), Unit Tests (Java 11/17/21), Title Check. Locally
./gradlew spotlessCheck test compileExamples→ 123 passed / 1 skipped (the skip is the opt-inSpeakV2WebSockettest; withDEEPGRAM_SPEAK_V2_WS=1it passes, makingintegrationTest7/7).Ships 2 breaking source changes, both documented in
docs/Migrating-v0.8-to-v0.9.md. Both are source-level renames with identical wire values, so no request payload changes and a clean recompile is the whole migration.New in this regen
sendForceEndTurn()+ListenV2ForceEndTurn, plusListenV2TurnInfo.getTrigger()(Optional<String>, open-typed) identifying what ended a turn. Now enabled on staging and verified end-to-end; still gated on production — see Force-end-turn: verified on staging, gated on production.getDiarizeInfo()on the response/results metadata (getModelUuid(),getArch()) identifying which diarizer ran, plus per-wordgetSpeakerConfidence()(Optional<Float>) on pre-recorded words items.speaker_confidenceis pre-recorded only, not streaming.DeepgramModelconstants, including the Flux TTS voices.*ThinkProvider*/*SpeakProvider*prefixes and consolidated the agent conversation-history leaf types.Force-end-turn: verified on staging, gated on production
Updated 2026-08-20 — this supersedes the earlier "cannot be tested end-to-end" finding. The gate has since opened on staging, so the feature is now verified against a live server through the SDK's own
V2WebSocketClient(not a raw socket): stream real audio, force the turn to end mid-sentence, read backtrigger.api.staging.deepgram.comapi.deepgram.comTurnInfoflowingTurnInfoflowing{"type":"ForceEndTurn"}UNPARSABLE_CLIENT_MESSAGE— "The ForceEndTurn message is not enabled on this deployment.", socket closedEndOfTurnafter the forcetrigger=manual,end_of_turn_confidence=0.0081EndOfTurn(no force)trigger=model,end_of_turn_confidence=0.773triggerabsentTwo details confirm the feature genuinely works rather than the turn coincidentally ending:
end_of_turn_confidence=0.0081on the forced turn. The model was nowhere near ending it — compare0.773at a natural end. The turn ended because we asked.StartOfTurnfired again, and transcription continued. That is the intended semantic, not a disconnect.No SDK change was needed.
sendForceEndTurn()serialized correctly andgetTrigger()deserialized bothmanualandmodelunmodified. Typingtriggeras an openOptional<String>rather than a closed enum is what made that hold.Correcting the earlier evidence
The previous revision argued the gate was closed everywhere partly because "
triggercame back absent on everyTurnInfo." That was weaker than it read:triggeris documented as present onEndOfTurnand only there, and any probe that sendsForceEndTurnto a gated deployment gets its socket closed before reaching anEndOfTurn— so the observation was equally consistent withtriggerworking fine. The bottom row above closes that hole by streaming through to a naturalEndOfTurn: on productiontriggeris still absent there, which is the claim that actually needed support.Caveat, unchanged: one API key per environment, so this shows the gate is open for that staging project. Whether enablement is per-project, per-deployment, or global still needs someone with the feature-flag config.
This is also why #91 reverted force-end-turn previously. The difference now is that the typed surface ships with an accurate enablement warning — and, on a deployment that has it, live coverage.
Breaking changes (documented, not shimmed)
Java has no type aliases, so there is no way to keep the old names as exact aliases of the new ones. A deprecated constants-only shim class would fix inline call sites like
.model(DeepgramSpeakProviderModel.AURA2ASTERIA_EN), but it cannot preserve a return type — andOptional<GoogleThinkProviderVersion> v = google.getVersion()is the exact line 0.8.0's own migration guide told users to write. Binary compatibility is broken regardless, so every consumer recompiles. This repo has no@Deprecatedanywhere and shipped 0.8.0 asfeat(regen)!with a migration guide; this cycle follows that convention.9 renames (constant names and wire values unchanged):
AnthropicThinkProviderModelAnthropicModelCartesiaSpeakProviderModelIdCartesiaModelIdCartesiaSpeakProviderVoiceCartesiaVoiceDeepgramSpeakProviderModelDeepgramModelGoogleThinkProviderModelGoogleModelGoogleThinkProviderVersionGoogleVersionGroqThinkProviderReasoningModeGroqReasoningModeAgentV1HistoryContentRoleConversationHistoryMessageRoleAgentV1HistoryFunctionCallsFunctionCallsItemFunctionCallHistoryMessageFunctionCallsItemOpenAiThinkProvider*is not renamed (it collides, so the generator keeps the long form).2 duplicate types removed —
AgentV1SettingsAgentContextContextMessagesItemContentRoleand...FunctionCallsFunctionCallsItemare replaced by the canonicalConversationHistoryMessageRole/FunctionCallHistoryMessageFunctionCallsItem. Verified structurally identical: same constants, same"user"/"assistant"wire values.The ripple is wider than the type list implies. A
javapsurface diff over all 1688 public types at 0.8.0 vs HEAD found 53 removed public types and 30 removed public members across 21 surviving types — because seven carrier types changed both getter return types and builder parameter types:Anthropic,Cartesia,Deepgram,Google,Groq,ConversationHistoryMessage,FunctionCallHistoryMessage. The migration guide lists each with its affected members.Every removal traces to those 11 renamed/deleted types — zero unexplained removals, and no new required builder stages, so there is no 0.8.0-style "gained a required field" break hiding in here.
Two generator regressions patched, not shipped
speak.v2connectspeedThe generator retyped
V2ConnectOptions.speedfromOptional<Double>to a closed 7-value string-literal enum. Not shipped — restored toDoubleand frozen, so existing.speed(1.05)callers are unaffected.The generated type contradicts the API contract two ways: the mid-stream
SpeakV2Configure.speedon the same connection is stillOptional<Double>, and the server parses the numeric form and range-checks it (SPEED_OUT_OF_RANGEoutside0.85–1.15,SPEED_INCREMENT_INVALIDoff the0.05increment) — so a closed enum cannot express a valid in-range value it omits. Guarded bySpeakV2ConnectWireTest; the generatedSpeakV2Speedtype is left in place, unused, as documentation of the accepted values, and it appears in no public signature. Unfreeze when the spec types the connectspeedas a number.DeepgramModel.FLUX_RENEE_ENThe generator removed this constant. Not shipped — restored and frozen. Verified against the live API rather than assumed:
POST /v2/speak?model=flux-renee-en→ 200 with a valid 3.1s MP3 (MPEG layer III, 48 kbps, 24 kHz mono — Flux TTS's output format).flux-bogus-nonexistent-enis rejected withINVALID_QUERY_PARAMETER, whileflux-renee-enis accepted.So the removal is a spec regression, not a retirement. All five touchpoints restored (constant,
Valueenum,visit()case,valueOf()case,Visitormethod); guarded by aRegenTypesTestcase asserting the wire value,valueOf()resolution, and JSON round-trip. The 25 additions are all retained — the enum now has 100 constants, with nothing removed relative to 0.8.0.DeepgramModelcarries a risk the other frozen files don't: it receives frequent additive spec changes, so restoring its.bakwholesale on a future regen would silently drop new voices..fernignoreandAGENTS.mdboth carry an explicit warning to diff and carry additions forward instead.Patch reconciliation
4.18.0 fixed none of the five existing defect groups. All 20 frozen files re-patched, all 20
.fernignorepaths restored; 3 files newly frozen (V2ConnectOptions,ListenV2ForceEndTurn,DeepgramModel). Every.bakdeleted.hashCode()equals()with nohashCode()defaultImpldefaultImpl = _UnknownValue.classaddQueryParameter("keyterm", String.valueOf(...))additionalPropertiesconnect()still never emits themonErrorReconnectingWebSocketListenerretryCount >= maxRetries, hardcoded4000, noapplyOptionsOverrideListenV2ForceEndTurnreached the branch unpatched. It is a 12th fields-less type withequals()and nohashCode(). Diffing.bakfiles structurally cannot catch this — the type was reverted out in #91 and reintroduced by 4.18.0, so it was newly generated with no.bakto compare against. Now patched, frozen, and asserted byRegenTypesTest. A post-regen sweep step was added toAGENTS.mdso this class of miss cannot recur.applyOptionsOverrideis load-bearing.core/transport/TransportWebSocketFactory.java:39calls it and is permanently frozen, so main source does not compile without the patch.ClientOptionsstays frozen. 4.18.0 replaced the version literals withgetSdkVersion()readingPackage.getImplementationVersion(). That does resolve in the published artifact (CI runsmvn deploy -P release;pom.xml's maven-jar-plugin setsaddDefaultImplementationEntries=true), but resolves tonullunder Gradle and in tests, falling back to a generator-authored literal nothing keeps current..github/release-please-config.jsonalready lists this file inextra-files, so the// x-release-please-versionmarkers are actively wired. Also keepsUser-Agenton thecom.deepgram:coordinate form to matchX-Fern-SDK-Name.AGENTS.mdvalidation command corrected to./gradlew spotlessCheck test compileExamples. CI runs Spotless as its own job, so the previously documentedtest compileExamplespassed locally while CI failed — which happened on this branch. Hand-re-applying patches trips the formatter routinely, since inserted comments and renamed types change how chained calls wrap.Verification
unitTest→ 106 passed, 0 skipped. Every patch group's regression guard passes:applyOptionsOverride(2),maxRetries(0)(1),ReconnectOptions builder(4), fields-lesshashCode()(1),FLUX_RENEE_EN(1),AgentSettingsProviderDefaultTest(3), both forward-compat tests, and the wire tests (ListenV17,ListenV29,SpeakV27,StreamingAdditionalProperties4,QueryStringMapper12).integrationTest→ 8/8 withDEEPGRAM_SPEAK_V2_WS=1andDEEPGRAM_LISTEN_V2_FORCE_END_TURN=1against staging; 6/8 with 2 skips against production, where both gated features are unavailable. There is no separate e2e suite — the live-API integration tests plus the examples are that layer.manageexamples run (31 previously, plus the newlisten/ForceEndTurn). 20 exit clean; 7 time out and 4 fail, and every one of those 11 reproduces identically on a 0.8.0 worktree, so there are no example regressions. The failures are environmental (no local proxy, a missing CLI argument, a missing audio file, a placeholder callback URL) and the timeouts are streaming examples that finish their work but leave a non-daemon executor holding the JVM open, plus three agent examples hitting server-sideCLIENT_MESSAGE_TIMEOUT/INVALID_SETTINGS. All pre-existing.javapdiff vs 0.8.0 confirms the breaking scope is bounded to the renames.flux-renee-enconfirmed serving; force-end-turn confirmed working on staging (trigger=manual) and still gated on production.Version bump
Title is
feat(regen)!so release-please computes a minor bump to0.9.0from the squash-merge message. The!matters — the migration guide is written for0.8→0.9.Test coverage added
The new surface from this regen arrived untested; that is now closed.
ListenV2ControlFrameWireTest(new) —sendForceEndTurnhad no test at all. Asserts the frame arrives, that it carries nothing but the discriminator (exact match, since the server rejects unknown fields on control messages), andsendCloseStreamas a control so a future failure separates "send path broke" from "ForceEndTurn broke". This runs on every build regardless of deployment gating, so it stays the baseline guard wherever the live test skips.RegenTypesTest.Regen20260819(new) — 7 cases overListenV2TurnInfo.trigger, bothDiarizeInfovariants, and words-itemspeaker_confidence, parsed from realistic server payloads rather than builder round-trips. Each is also asserted absent, since absence is normal:triggerappears only onEndOfTurn,diarize_infoonly with diarization on, andspeaker_confidenceis pre-recorded only. Thetrigger=manualfixture matches what the live server returns verbatim.IntegrationTest.testIntegration_ListenV2ForceEndTurn(new) — the first live-API coverage of the listen v2 WebSocket in this repo, which previously had none at all. Streams real audio, waits forStartOfTurnso there is a turn to end, forces it, and assertstrigger=manualwith no server error. Opt-in viaDEEPGRAM_LISTEN_V2_FORCE_END_TURN=1, so it skips cleanly where the feature is gated rather than failing the build — same pattern as the existingDEEPGRAM_SPEAK_V2_WStest. Verified passing against staging and skipping against production.examples/listen/ForceEndTurn.java(new) — no example usedsendForceEndTurn, andLiveStreamingV2never actually streams audio, so no example reached aTurnInfoat all. This one streams real audio, forces a turn end, and prints thetrigger. On a gated deployment it reports that and exits cleanly rather than crashing. Verified against both.IntegrationTest— theSpeakV2WebSockettest now setsspeed(1.05), so thespeedpatch is exercised against a real server rather than only a mock. Verified passing live.⚠ Merging: the squash message needs a
BREAKING CHANGE:footerThis is required, not optional. release-please builds the
### ⚠ BREAKING CHANGESsection ofCHANGELOG.mdfrom a literalBREAKING CHANGE:footer in the squash-merge commit body — that is how 0.8.0's entry got its text. No commit on this branch has one, and the!in the title alone only triggers the version bump; it does not produce the explanatory text or the migration-guide link. Merging with GitHub's default (this whole review document as the commit body) would ship 0.9.0 with an unexplained breaking-changes section.Use this as the squash-merge message instead:
Unlike 0.8.0, the release-please range is clean —
v0.8.0..mainis empty, so this squash commit will be its only input and there is no reverted-commit pairing to hand-edit out of the release PR this time.Follow-ups
speedretype and the droppedFLUX_RENEE_ENconstant. That is two items on top of the six existing open Fern requests, none of which 4.18.0 addressed.integrationTestwithDEEPGRAM_LISTEN_V2_FORCE_END_TURN=1against production once the gate lifts there; the test is written to need no changes when it does, and thetriggerwording in the migration guide can drop its enablement caveat at that point.