revert: listen v2 force-end-turn/redaction + diarize regen (#750) - #757
Conversation
dg-coreylweathers
left a comment
There was a problem hiding this comment.
Approving. Verified this is a pure inverse of #750 — git diff 7da415d 63e25d6 is empty, so the tree lands byte-identical to the v7.6.0 tag. No collateral.
Checked independently rather than relying on CI:
- No customer impact. PyPI latest is 7.6.0; #750 bumped
pyprojectto 7.6.1 but never published. Rated against the last released SDK this is non-breaking — no semver event. - Gate passes on
63e25d6: pytest 331 passed / 1 skipped, mypy clean across 857 source files (run in apython:3.12-slimcontainer, full wire suite via an external WireMock). - Release plumbing stays coherent. The revert restores both halves of a matched pair — the
genericclient_wrapper.pyentry inrelease-please-config.jsonand the# x-release-please-versionannotations on theUser-Agent/X-Fern-SDK-Versionlines. No wire-telemetry version drift. - #758 restores what this drops: reconnection plumbing,
importlib.metadataversion resolution, redaction, and all the compat-alias shims.
Two non-blocking notes on the description (code is fine, merge away):
1. Scope is wider than the title. #750 was a full generator cycle, not three spec features — it also carried stream-reconnection plumbing (stream_reconnection_enabled / max_stream_reconnection_attempts) and ~446 net lines across src/deepgram/core/ (http_sse/_api.py alone +365). All of that reverts too, and all of it returns in #758. Worth a line in the body so the next reader of main's history knows why core/ moved.
2. The "Preserved" paragraph points somewhere slightly wrong. gh/sdk-gen-2026-08-11 is stacked on top of this revert, and its tip is a fresh regen rather than a replay of #750 — grepping src/deepgram there gives 0 files for force_end_turn and 0 for diarize_info (redaction and the infra do survive). Nothing is orphaned, since 4678f0e stays an ancestor of that branch, but "re-apply from the parked branch" would quietly hand someone a regen missing two of the three features. Suggest naming 4678f0e as the recovery point for those two specifically.
One thing to settle before the next release, not a blocker on this PR: #751 currently announces "Listen v2 turn control & redaction" as shipped in 7.7.0, citing 4678f0e. If it merges after this lands, we publish a changelog describing features that aren't in the tree. Suggest closing #751 now and letting #758 drive the next version, rather than letting release-please re-open it as a no-op 7.6.1 whose artifact is identical to 7.6.0. Your call on the release infra.
#91) Reverts the listen v2 regen (`5b6323a`, originally #89: Flux force-end-turn, listen v1 diarize metadata + arch, word speaker confidence, `AgentV1UpdateListen` provider retype, `ListenV2Redact`) off `main`. Mirrors the equivalent revert on deepgram-python-sdk: deepgram/deepgram-python-sdk#757 ## Why This work isn't ready to ship yet, and other priorities need to release from `main` without it. Since the regen is already on `main`, any release cut from `main` (including the pending release-please 0.7.2, #90) would publish it. Reverting removes it from the release line cleanly, without rewriting history. ## Redoing the regen The regen commit `5b6323a` stays reachable in history (and on the #89 branch), and the next regen branch `gh/sdk-gen-2026-08-11` (#92) is stacked on this revert. **Caveat:** re-landing is a fresh 4.16.0 regen, not a replay of #89's hand-patches — see "Forfeited by this revert" below. ## Also reverted (not spec features) Beyond the five spec features, this 184-file revert also rolls back: - **Fern toolchain downgrade** (`.fern/metadata.json`): `cliVersion` 5.89.0 → 5.44.6, `generatorVersion` 4.16.0 → 4.10.1, `runtime-version` dropped. This is what makes most of the rest follow. - **Restores** the `ClientOptions.java` `.fernignore` freeze block + its release-please `generic` extra-file entry (both dropped by #89) — this is what keeps `X-Fern-SDK-Version` release-please-bumpable (no wire-version drift). - **Removes** the three 4.16.0-era stopgap freeze blocks (type-rename, return-type shim, union default-variant) and the `ListenV2ForceEndTurn` `hashCode()` entry from `.fernignore` and `AGENTS.md`. - **`README.md`**: removes the 4.16.0 retry-knob docs (retry-behavior prose that remains accurate has been restored — see the diff). ## Forfeited by this revert (tracked in #93) The revert also drops patches #89 carried that are **not** preserved on `gh/sdk-gen-2026-08-11` and **not** among #92's 17 reconciled patches (verified: all three unions read `defaultImpl = _UnknownValue.class` on #92, both tests absent): - **Union default-variant fix** (`defaultImpl = V2Value`) on `AgentV1UpdateListenListenProvider`, `AgentV1SettingsAgentListenProvider`, `AgentV1SettingsAgentContextListenProvider`. The two Settings unions carry this bug in **released 0.7.1** (pre-existing, not a regression), so the revert restores released behavior — it forfeits an *unreleased* fix. - **`AgentV1UpdateListenListen.getProvider()` additive return-type shim.** - **Regression guards** `AgentV1UpdateListenShimTest.java`, `AgentSettingsProviderDefaultTest.java`. Whether to re-apply the `defaultImpl` fix on #92 is a deliberate decision tracked in #93. ## Verification - `./gradlew compileJava compileExamples unitTest` — build successful, all unit tests pass. - The revert is a clean inverse of the single regen commit (184 files): `git diff 5b6323a^ HEAD` over the pre-README-fix tree is empty (byte-identical to the 0.7.1 release commit `8b3c605`). Restores pre-regen `.fern/metadata.json` (sdkVersion 0.7.1, generator 4.10.1), the `.fernignore` freeze blocks, and the `release-please-config.json` `ClientOptions.java` generic entry. - Escape-hatch (`additionalProperties`), forward-compat no-op, and fields-less `hashCode()` patches from 0.7.1 all survive.
…n v2 redaction (#758) Python SDK regeneration from the `jherlihy/flux-tts-ga` docs branch (fern-python-sdk 5.23.0). ## What this adds **Speak v2** - `send_interrupt()` — barge-in, optionally with a `playback_offset` (`{type: "time_ms", value: N}`) - `send_configure()` — mid-stream `speed` changes, acknowledged by `ConfigureSuccess` / `ConfigureFailure` - `SpeechInterrupted` server message, with metadata incl. a new `controls_applied.breaks_applied` counter - `speed` and `expressivity` connect query parameters **Listen v2** - `redact` connect parameter (`ListenV2Redact`: `numbers`, `aggressive_numbers`) - `send_configure()` is now properly typed (`ListenV2Configure` + `ListenV2ConfigureSuccess` in the response union), replacing the previous `typing.Any` shim **Other** - `GoogleThinkProviderVersion` (adds `ai-studio-v1beta`, `gemini-enterprise-agent-v1`) - `AgentV1UpdateListenListenProvider` discriminated union (`_V1`/`_V2`, discriminant `version`) - `deepgram` speak provider `version` widened from `Literal["v1"]` to `str` - `client_wrapper` now derives its version from `importlib.metadata` instead of a hardcoded string ## Manual patch reconciliation **Re-applied** (generator still misses these): socket-client broad `except` (required for custom transports, which raise non-`websockets` errors) and the optional no-payload control params; agent-settings and `language_hint` back-compat validators; `query_encoder` bool coercion; the 10 `__init__.py` legacy alias re-exports (merged with the new generated exports); wire-test legacy create-key alias coverage. **Dropped** (generator now covers them, entries removed from `.fernignore`): - `agent_v1latency_report` — `stt_latency` is emitted natively again - `core/client_wrapper` — dynamic versioning supersedes the `x-release-please-version` annotations; its `generic` extra-file entry is also removed from `release-please-config.json` **Recovered**: the `agent_v1update_listen_listen.py` provider-coercion validator was missing from `.fernignore` and was overwritten by the generator. Restored and now frozen — a regression test caught it. `listen/v2 send_configure` keeps runtime tolerance for a raw dict (sent verbatim) for back-compat with pre-typed-model callers. ## Compatibility No breaking changes against the last release (**v7.6.0**): 0 removed public exports, 0 deleted modules, all baseline socket-client signatures intact, and enum changes are widenings only. Compat alias shims still pass. The one change that *would* have broken callers is `AgentV1UpdateListenListen.provider` (bare `DeepgramListenProviderV2` -> required discriminated union). The restored validator coerces a legacy provider instance *or* a legacy bare dict into the new shape, verified at runtime — both produce `version: "v2"` on the wire. ## Review follow-ups (b8b2e78) - **AGENTS.md freeze ledger synced with `.fernignore`.** Removed the now-unfrozen `client_wrapper.py` entry, added the newly-frozen `agent_v1update_listen_listen.py` (with a note that this patch was silently lost once by a regen), added the three `language_hint` provider validators and the new frozen test file, and corrected the `listen/v2/socket_client.py` entry which still described the superseded `typing.Any` `send_configure` shim. Verified zero drift in both directions. - **`speak/v2 send_interrupt` now takes an optional message**, matching the sibling no-payload controls (`send_flush`/`send_close`) — `SpeakV2Interrupt` has no required fields, so `send_interrupt()` is meaningful. `send_configure` deliberately keeps its required argument (a Configure with no settings is meaningless). Covered by new sync + async tests. ### Version bump The branch carries `7.6.1` (a patch) but this is a `feat`. That's expected: the generator patch-bumps `pyproject.toml` on every regen and release-please recomputes the correct minor at release time from the conventional-commit type. Verified across three cycles — e.g. `#750` left `7.6.1` and release-please's PR proposed `7.7.0`. This only holds because the squash-merge message is `feat(...)`, which is why the PR title matters here. ## Review follow-ups (round 2) **[B1] blocking — fixed.** Passing `provider` as a plain dict to `AgentV1UpdateListenListen` skipped the `language_hint` → `language_hints` remap, so the deprecated singular key reached the wire. The API uses `deny_unknown_fields`, so the whole `UpdateListen` was rejected with no client-side signal. The dict branch now routes through `DeepgramListenProviderV1/V2` so their own before-validators run — one remap implementation instead of two. **[S1]** fixed in the same edit: the version is inferred from the dict's own keys (`language`/`smart_format` are v1-only) rather than blanket-stamped `v2`. One deviation from the suggested fix: constructing the shim model *raises* on a malformed dict (e.g. missing the required `model`), which would have made the compat shim a new source of exceptions. Wrapped with a pass-through fallback, so behaviour is never worse than pre-regen. The downstream union rejection of a genuinely-invalid payload was confirmed pre-existing (the old code produced an identical payload, rejected identically). **[S3] restored wire coverage.** The regen dropped all ten optional query params from the `manage.v1.projects.requests.list` wire test (Fern derives the test from the spec *example*, which was simplified upstream), leaving their serialisation — including `datetime` → ISO-8601 `Z` — unverified while the client still forwarded them. Restored and frozen in `.fernignore` with the same rationale as the `_keys.py` entry. **[S2] reference.md updated.** Added the five missing entries (`redact`, `send_configure` for listen v2; `speed`, `expressivity`, `send_interrupt`, `send_configure` for speak v2). The param entries spell out accepted values, since the spec's numeric enums are dropped in codegen. **[S7] barge-in example added** — `examples/26-text-to-speech-barge-in-v2.py`, async (the sync client blocks in `start_listening()`, so mid-stream interrupt can't be shown).⚠️ **Not yet verifiable**: `speak.v2.connect(model="flux-alexis-en")` returns HTTP 400 on `api.deepgram.com` today, consistent with the API not being in production yet. **[S8] documented** — AGENTS.md now records the residual concerns on the two newly-unfrozen files, notably that `client_wrapper`'s version fallback literal is refreshed only by regen. ## Security fix (found while running the new example) Every websocket `connect()` path raises `ApiError(headers=dict(headers), ...)` with the **full request headers**, and both `ApiError` and `ParsingError` stringify that dict. A failed connect therefore printed: ``` headers: {'Authorization': 'Token <live key>', ...}, status_code: 400, ... ``` so any application logging the exception — `print(e)`, a traceback, a log aggregator, an error tracker — wrote the customer's API key in clear text. This is the same threat `_secure_logging.py` already covers for the `websockets` DEBUG handshake logs; the exception path is simply the other route to it, and was uncovered. It also defeats the telemetry scrubber, which strips `request.headers` but not the exception *message*. Both error types now mask credential values **at construction** (so `repr()` and attribute serialisation are covered too, not just `__str__`), reusing `_secure_logging`'s existing sensitive-header set and scheme-preserving mask. Non-sensitive headers are kept — `dg-request-id` is the main reason to inspect them. Both files are generated, so they're frozen with the rationale and documented in the ledger. 14 new tests, mutation-checked. **Pre-existing, not introduced by this regen**, and it affects all five socket clients. ## Core / generator upgrade (5.14.18 → 5.23.0) Nine generator minors also changed core HTTP behaviour that the feature summary above doesn't cover: 1. **`RequestOptions` gains `timeout`**, and `timeout_in_seconds` becomes a deprecated alias — a new public option plus a deprecation. 2. **Behaviour change for custom `httpx_client` users.** The SDK previously adopted your client's `.timeout.read` as the base timeout for every request; the base timeout is now `None`, resolving to `httpx.USE_CLIENT_DEFAULT`. This is an improvement — the old path flattened a granular `httpx.Timeout(connect=5, read=30, …)` down to a single `30`, silently widening the connect timeout — but if you tuned around that flattening, expect connect timeouts to tighten to what you actually configured. 3. **`get_keepalive_socket_options()`** is a new public helper in `core/http_client.py` that nothing in the SDK calls. 4. **`stream_reconnection_enabled` / `max_stream_reconnection_attempts`** are added to both client constructors and to `RequestOptions`, documented as "Defaults to True" — but they are consumed only by `core/http_sse/_api.py`, and nothing wires `http_sse` to an endpoint, so **they are inert today and do not apply to the WebSocket clients**. Worth knowing, since `DeepgramClient` already has an unrelated hand-written `reconnect` flag. (Not patched in the docstring: those files are generated and now unfrozen, so a comment there would be lost next regen.) ## Spec copy fixes — [S4] / [S5] (done, not deferred) Both are spec `description` fields, so the durable fix is in `deepgram-docs`: **deepgram-docs#1094**, stacked on the Flux GA PR #1090 (`SpeakV2Speed`/`SpeakV2Expressivity` don't exist on docs `main` yet, so there was nothing to edit there). To avoid this release shipping stale copy while waiting on a regen, the **same text is applied by hand here**: - **[S4] Flux TTS "Early Access" wording removed** from the Deepgram agent speak-provider descriptions (`types/deepgram.py` + `requests/deepgram.py`). Flux is GA; the direct Speak V2 path had already been updated, so the Agent path was the only surface still telling developers Flux might change before GA. `src/` now has **zero** "Early Access" occurrences. No "GA" wording added in its place. - **[S5] accepted values stated** for `speed` and `expressivity`. Both *are* enums in the spec — the problem is downstream: Fern drops numeric enums, so `speed`'s "Only the multipliers listed here are accepted" pointed at a list that wasn't there and `expressivity` gave no range at all. Both now spell the values out, and `speed` names the two failure codes (`SPEED_OUT_OF_RANGE`, `SPEED_INCREMENT_INVALID`). These four files are **not frozen**. The hand-applied text was verified to match #1094 byte-for-byte — the spec YAML was parsed, folded scalars normalised the way Fern renders them, and all five descriptions (provider, version, model, speed, expressivity) compared equal — so the next regen emits identical output and a freeze would protect nothing. Fern keeps ownership; no maintenance debt once #1094 merges. ## Known issues NOT fixed here - **Numeric spec enums lose their constraints in codegen** (`SpeakV2Speed` → bare `float`, `SpeakV2Expressivity` → bare `int`; string enums keep their `Literal` unions), so out-of-range values are caught only by the server. Documented rather than hand-validated — the `reference.md` entries and the corrected docstrings now state the accepted values. Worth raising with Fern, since it affects every Deepgram SDK. - **[N2] `.fern/metadata.json` records `originGitCommitIsDirty: true`** — the spec input isn't reproducible from version control. Pre-existing practice (`main` carries the same flag) and unchanged here, since fixing it needs a spec commit plus a regen. - **[S7] the new barge-in example is unverified against a live API.** `speak.v2.connect(model="flux-alexis-en")` returns HTTP 400 on `api.deepgram.com` today, consistent with the merge gate below. ## Note on the `ApiError` fix and the other SDKs The credential leak above is **Python-specific** and does not need porting. It required two Python-only things to coincide: `ApiError.__str__` printing `self.headers`, *and* the websocket `connect()` paths passing the **request** headers. Checked both siblings: the JS `DeepgramError.buildMessage({message, statusCode, body})` takes no headers and none of its construction sites pass request headers; Java's `DeepgramHttpException` populates its header map from `rawResponse.headers()` (response headers) and has no `toString` that dumps them. ## Verification - `pytest` — **375 passed**, 1 skipped - `ruff check src/` clean; `mypy src/` (882 files) and `mypy tests/typecheck/` clean - **18 new tests** covering the added surface, each mutation-verified (deliberately broke the source and confirmed the test fails). Note these models are `extra="allow"`, so asserting a field's *value* does not detect a dropped field *declaration* — the metadata tests assert the declared field set as well. - **16 examples run against the live API**, including `25-text-to-speech-streaming-v2.py` (Speak v2 streaming) and `30-voice-agent.py`. Management examples were intentionally skipped; 5 others need a local audio file, a microphone, or the separate `deepgram-sagemaker` package. Rebased onto `main` after #757 (the listen-v2 revert) merged, so the revert is no longer duplicated on this branch. --------- Co-authored-by: fern-api[bot] <115122769+fern-api[bot]@users.noreply.github.com>
Reverts the listen v2 regen (
4678f0e, originally #750: force-end-turn, redaction, listen v1 diarize metadata) offmain.Why
This work isn't ready to ship yet, and other priorities need to release from
mainwithout it. Since the regen is already onmain, any release cut frommain(including the pending release-please 7.7.0, #751) would publish it. Reverting removes it from the release line cleanly, without rewriting history.Preserved
The regen is not lost — it's parked on
gh/sdk-gen-2026-08-11(branched frommainat4678f0e) and can be re-applied / re-released when it's ready.Follow-ups
gh/sdk-gen-2026-08-11(a revert-of-this-revert, or a fresh regen).