Skip to content

feat(regen)!: listen v2 force-end-turn + diarize metadata; rename provider/agent-history types - #96

Open
GregHolmes wants to merge 12 commits into
mainfrom
gh/sdk-gen-2026-08-19
Open

feat(regen)!: listen v2 force-end-turn + diarize metadata; rename provider/agent-history types#96
GregHolmes wants to merge 12 commits into
mainfrom
gh/sdk-gen-2026-08-19

Conversation

@GregHolmes

@GregHolmes GregHolmes commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Fern SDK regeneration for 2026-08-19 (fern-java-sdk 4.16.0 → 4.18.0, CLI 5.44.6 → 5.95.1, spec 03f0677068de88).

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 compileExamples123 passed / 1 skipped (the skip is the opt-in SpeakV2WebSocket test; with DEEPGRAM_SPEAK_V2_WS=1 it passes, making integrationTest 7/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

  • Listen V2 force-end-turnsendForceEndTurn() + ListenV2ForceEndTurn, plus ListenV2TurnInfo.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.
  • Listen V1 diarization detailgetDiarizeInfo() on the response/results metadata (getModelUuid(), getArch()) identifying which diarizer ran, plus per-word getSpeakerConfidence() (Optional<Float>) on pre-recorded words items. speaker_confidence is pre-recorded only, not streaming.
  • 25 new DeepgramModel constants, including the Flux TTS voices.
  • Provider/agent-history type renames — the generator dropped the *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 back trigger.

Probe api.staging.deepgram.com api.deepgram.com
Baseline connect + stream audio Connected, TurnInfo flowing Connected, TurnInfo flowing
{"type":"ForceEndTurn"} Accepted — no error, socket stays open UNPARSABLE_CLIENT_MESSAGE"The ForceEndTurn message is not enabled on this deployment.", socket closed
EndOfTurn after the force trigger=manual, end_of_turn_confidence=0.0081 never reached — socket closed first
Natural EndOfTurn (no force) trigger=model, end_of_turn_confidence=0.773 trigger absent

Two details confirm the feature genuinely works rather than the turn coincidentally ending:

  • end_of_turn_confidence=0.0081 on the forced turn. The model was nowhere near ending it — compare 0.773 at a natural end. The turn ended because we asked.
  • The stream survives and advances. After the forced end the turn index incremented, StartOfTurn fired again, and transcription continued. That is the intended semantic, not a disconnect.

No SDK change was needed. sendForceEndTurn() serialized correctly and getTrigger() deserialized both manual and model unmodified. Typing trigger as an open Optional<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 "trigger came back absent on every TurnInfo." That was weaker than it read: trigger is documented as present on EndOfTurn and only there, and any probe that sends ForceEndTurn to a gated deployment gets its socket closed before reaching an EndOfTurn — so the observation was equally consistent with trigger working fine. The bottom row above closes that hole by streaming through to a natural EndOfTurn: on production trigger is 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 — and Optional<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 @Deprecated anywhere and shipped 0.8.0 as feat(regen)! with a migration guide; this cycle follows that convention.

9 renames (constant names and wire values unchanged):

v0.8.x v0.9.0
AnthropicThinkProviderModel AnthropicModel
CartesiaSpeakProviderModelId CartesiaModelId
CartesiaSpeakProviderVoice CartesiaVoice
DeepgramSpeakProviderModel DeepgramModel
GoogleThinkProviderModel GoogleModel
GoogleThinkProviderVersion GoogleVersion
GroqThinkProviderReasoningMode GroqReasoningMode
AgentV1HistoryContentRole ConversationHistoryMessageRole
AgentV1HistoryFunctionCallsFunctionCallsItem FunctionCallHistoryMessageFunctionCallsItem

OpenAiThinkProvider* is not renamed (it collides, so the generator keeps the long form).

2 duplicate types removedAgentV1SettingsAgentContextContextMessagesItemContentRole and ...FunctionCallsFunctionCallsItem are replaced by the canonical ConversationHistoryMessageRole / FunctionCallHistoryMessageFunctionCallsItem. Verified structurally identical: same constants, same "user"/"assistant" wire values.

The ripple is wider than the type list implies. A javap surface 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.v2 connect speed

The generator retyped V2ConnectOptions.speed from Optional<Double> to a closed 7-value string-literal enum. Not shipped — restored to Double and frozen, so existing .speed(1.05) callers are unaffected.

The generated type contradicts the API contract two ways: the mid-stream SpeakV2Configure.speed on the same connection is still Optional<Double>, and the server parses the numeric form and range-checks it (SPEED_OUT_OF_RANGE outside 0.851.15, SPEED_INCREMENT_INVALID off the 0.05 increment) — so a closed enum cannot express a valid in-range value it omits. Guarded by SpeakV2ConnectWireTest; the generated SpeakV2Speed type is left in place, unused, as documentation of the accepted values, and it appears in no public signature. Unfreeze when the spec types the connect speed as a number.

DeepgramModel.FLUX_RENEE_EN

The generator removed this constant. Not shipped — restored and frozen. Verified against the live API rather than assumed:

  • POST /v2/speak?model=flux-renee-en200 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.
  • It also appears in a July 2026 internal 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. All five touchpoints restored (constant, Value enum, visit() case, valueOf() case, Visitor method); guarded by a RegenTypesTest case 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.

⚠️ Freezing DeepgramModel carries a risk the other frozen files don't: it receives frequent additive spec changes, so restoring its .bak wholesale on a future regen would silently drop new voices. .fernignore and AGENTS.md both 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 .fernignore paths restored; 3 files newly frozen (V2ConnectOptions, ListenV2ForceEndTurn, DeepgramModel). Every .bak deleted.

Patch group Files Evidence in 4.18.0 output
Fields-less hashCode() 11 → 12 Still emits equals() with no hashCode()
Union defaultImpl 3 Still defaultImpl = _UnknownValue.class
WS array query-params 3 Still addQueryParameter("keyterm", String.valueOf(...))
WS additionalProperties 4 connect() still never emits them
Forward-compat no-op 2 Unknown frames still routed to onError
ReconnectingWebSocketListener 1 retryCount >= maxRetries, hardcoded 4000, no applyOptionsOverride

ListenV2ForceEndTurn reached the branch unpatched. It is a 12th fields-less type with equals() and no hashCode(). Diffing .bak files structurally cannot catch this — the type was reverted out in #91 and reintroduced by 4.18.0, so it was newly generated with no .bak to compare against. Now patched, frozen, and asserted by RegenTypesTest. A post-regen sweep step was added to AGENTS.md so this class of miss cannot recur.

applyOptionsOverride is load-bearing. core/transport/TransportWebSocketFactory.java:39 calls it and is permanently frozen, so main source does not compile without the patch.

ClientOptions stays frozen. 4.18.0 replaced the version literals with getSdkVersion() reading Package.getImplementationVersion(). That does resolve in the published artifact (CI runs mvn deploy -P release; pom.xml's maven-jar-plugin sets addDefaultImplementationEntries=true), but resolves to null under Gradle and in tests, falling back to a generator-authored literal nothing keeps current. .github/release-please-config.json already lists this file in extra-files, so the // x-release-please-version markers are actively wired. Also keeps User-Agent on the com.deepgram: coordinate form to match X-Fern-SDK-Name.

AGENTS.md validation command corrected to ./gradlew spotlessCheck test compileExamples. CI runs Spotless as its own job, so the previously documented test compileExamples passed 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

  • CI: all 8 checks green.
  • Unit: unitTest → 106 passed, 0 skipped. Every patch group's regression guard passes: applyOptionsOverride (2), maxRetries(0) (1), ReconnectOptions builder (4), fields-less hashCode() (1), FLUX_RENEE_EN (1), AgentSettingsProviderDefaultTest (3), both forward-compat tests, and the wire tests (ListenV1 7, ListenV2 9, SpeakV2 7, StreamingAdditionalProperties 4, QueryStringMapper 12).
  • Integration (live API): integrationTest8/8 with DEEPGRAM_SPEAK_V2_WS=1 and DEEPGRAM_LISTEN_V2_FORCE_END_TURN=1 against 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.
  • Examples: all 32 non-manage examples run (31 previously, plus the new listen/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-side CLIENT_MESSAGE_TIMEOUT / INVALID_SETTINGS. All pre-existing.
  • Rename safety: across all six renamed enums, 0 wire values changed and 0 constants lost — the renames are purely nominal.
  • API surface: javap diff vs 0.8.0 confirms the breaking scope is bounded to the renames.
  • Live API: flux-renee-en confirmed 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 to 0.9.0 from the squash-merge message. The ! matters — the migration guide is written for 0.80.9.

Test coverage added

The new surface from this regen arrived untested; that is now closed.

  • ListenV2ControlFrameWireTest (new) — sendForceEndTurn had 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), and sendCloseStream as 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 over ListenV2TurnInfo.trigger, both DiarizeInfo variants, and words-item speaker_confidence, parsed from realistic server payloads rather than builder round-trips. Each is also asserted absent, since absence is normal: trigger appears only on EndOfTurn, diarize_info only with diarization on, and speaker_confidence is pre-recorded only. The trigger=manual fixture 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 for StartOfTurn so there is a turn to end, forces it, and asserts trigger=manual with no server error. Opt-in via DEEPGRAM_LISTEN_V2_FORCE_END_TURN=1, so it skips cleanly where the feature is gated rather than failing the build — same pattern as the existing DEEPGRAM_SPEAK_V2_WS test. Verified passing against staging and skipping against production.
  • examples/listen/ForceEndTurn.java (new) — no example used sendForceEndTurn, and LiveStreamingV2 never actually streams audio, so no example reached a TurnInfo at all. This one streams real audio, forces a turn end, and prints the trigger. On a gated deployment it reports that and exits cleanly rather than crashing. Verified against both.
  • IntegrationTest — the SpeakV2WebSocket test now sets speed(1.05), so the speed patch is exercised against a real server rather than only a mock. Verified passing live.

⚠ Merging: the squash message needs a BREAKING CHANGE: footer

This is required, not optional. release-please builds the ### ⚠ BREAKING CHANGES section of CHANGELOG.md from a literal BREAKING 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:

feat(regen)!: listen v2 force-end-turn + diarize metadata; rename provider/agent-history types

Regenerates against Fern generator 4.16.0 -> 4.18.0 (CLI 5.44.6 -> 5.95.1,
spec 03f0677 -> 068de88), re-applies the hand-maintained patches, and
documents the resulting breaking changes with a migration guide.

Features:
- Listen v2 force-end-turn: sendForceEndTurn() + ListenV2ForceEndTurn, plus
  ListenV2TurnInfo.getTrigger() identifying what ended a turn. Requires
  server-side enablement and is rejected on staging and production today, so
  the typed surface ships but must not be depended on yet.
- Listen v1 diarization detail: getDiarizeInfo() (model_uuid, arch) on the
  response and results metadata, plus per-word getSpeakerConfidence() on
  pre-recorded words items.
- 25 new DeepgramModel voice constants, including the Flux TTS voices.

Two generator regressions are patched rather than shipped: the speak v2
connect `speed` param stays Optional<Double> (the generator retyped it to a
closed string enum, contradicting the numeric API contract), and
DeepgramModel.FLUX_RENEE_EN is restored (the voice is live and serving; the
removal was a spec regression).

BREAKING CHANGE: the provider and agent-history types lost their role prefixes — `AnthropicThinkProviderModel` → `AnthropicModel`, `CartesiaSpeakProviderModelId` → `CartesiaModelId`, `CartesiaSpeakProviderVoice` → `CartesiaVoice`, `DeepgramSpeakProviderModel` → `DeepgramModel`, `GoogleThinkProviderModel` → `GoogleModel`, `GoogleThinkProviderVersion` → `GoogleVersion`, `GroqThinkProviderReasoningMode` → `GroqReasoningMode`, `AgentV1HistoryContentRole` → `ConversationHistoryMessageRole`, `AgentV1HistoryFunctionCallsFunctionCallsItem` → `FunctionCallHistoryMessageFunctionCallsItem`. The duplicate `AgentV1SettingsAgentContextContextMessagesItemContentRole` and `...FunctionCallsFunctionCallsItem` are removed in favour of the canonical types. Getter return types and builder parameters on `Anthropic`, `Cartesia`, `Deepgram`, `Google`, `Groq`, `ConversationHistoryMessage` and `FunctionCallHistoryMessage` change accordingly. All wire values are unchanged, so no request payloads change. See docs/Migrating-v0.8-to-v0.9.md.

Unlike 0.8.0, the release-please range is clean — v0.8.0..main is 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

  • Two spec-side regressions are patched but not yet filed upstream: the speed retype and the dropped FLUX_RENEE_EN constant. That is two items on top of the six existing open Fern requests, none of which 4.18.0 addressed.
  • Force-end-turn is verified on staging but still gated on production. Re-run integrationTest with DEEPGRAM_LISTEN_V2_FORCE_END_TURN=1 against production once the gate lifts there; the test is written to need no changes when it does, and the trigger wording in the migration guide can drop its enablement caveat at that point.

fern-api Bot and others added 5 commits August 19, 2026 09:26
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.
@GregHolmes GregHolmes changed the title chore: SDK regeneration 2026-08-19 feat(regen)!: listen v2 force-end-turn, listen v1 diarize metadata; provider type renames Aug 19, 2026
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.
@GregHolmes GregHolmes changed the title feat(regen)!: listen v2 force-end-turn, listen v1 diarize metadata; provider type renames feat(regen)!: listen v2 force-end-turn + turn trigger, listen v1 diarize metadata; rename provider + agent history types Aug 19, 2026
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.
@GregHolmes GregHolmes changed the title feat(regen)!: listen v2 force-end-turn + turn trigger, listen v1 diarize metadata; rename provider + agent history types feat(regen)!: listen v2 force-end-turn + diarize metadata; rename provider/agent-history types Aug 19, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant