Commit be07aa6
fix(listen/v2): allow extra fields on TurnInfo models (5.3.x) (#736)
## Problem
The v2 listen (Flux) `TurnInfo` message can include additional
**per-word fields `start` and `end`** (type `double`) beyond the
`word`/`confidence` the 5.3.x models declare. This was observed
intermittently against the live API (roughly 1 in 6 connections in
testing); I have not confirmed the full server-side rollout scope.
The model `ListenV2TurnInfoEventWordsItem` declares only `word` +
`confidence` with `extra = "forbid"`, so any frame carrying extra fields
fails pydantic validation in `listen/v2/socket_client.py`
(`parse_obj_as(V2SocketClientResponse, ...)`).
Observed impact on the released 5.3.x line:
- **5.3.0**: uncaught `ValidationError` propagates out of
`start_listening()` (which only catches `WebSocketException` /
`JSONDecodeError`) and tears down the listen loop; it never reaches the
`EventType.ERROR` handler.
- **5.3.2 / 5.3.3**: `construct_type` doesn't raise, but the strict word
model still fails the union's validated pass, so the frame is silently
mis-resolved to `ListenV2ConnectedEvent` — transcript and words dropped,
no error (silent data loss).
## Fix
Relax `extra` from `"forbid"` to `"allow"` on
`ListenV2TurnInfoEventWordsItem` and `ListenV2TurnInfoEvent`, so unknown
additive fields are tolerated and retained. Model-agnostic — it doesn't
assume which model or which fields. Verified: a frame with `start`/`end`
then parses as `ListenV2TurnInfoEvent` with the fields preserved.
```diff
class Config:
frozen = True
- extra = "forbid"
+ extra = "allow"
```
(applied to both models in the file)
## Safety / side-effect analysis
- **No code-execution risk.** Pydantic `extra="allow"` only stores
unknown JSON values as data attributes (`__pydantic_extra__`); it does
not eval/import/instantiate types. Not pickle/YAML-style gadget
deserialization. Inbound data also comes from the Deepgram API over TLS,
not arbitrary user input.
- **Union resolution stays correct (tested).** `V2SocketClientResponse =
Union[Connected, TurnInfo, FatalError]`. `TurnInfo` has required fields
that `Connected`/`FatalError` lack, and those two keep `extra="forbid"`,
so a permissive `TurnInfo` cannot greedily capture them. Verified:
`Connected` → `ListenV2ConnectedEvent`, `Error` →
`ListenV2FatalErrorEvent`, `TurnInfo+start/end` →
`ListenV2TurnInfoEvent`.
- **Required-field validation preserved (tested).** A `TurnInfo` frame
missing a required field (e.g. `transcript`) still raises
`ValidationError`. Only *unknown additive* fields are now tolerated.
- **Memory:** negligible — `json.loads` already materializes the full
payload before pydantic runs; `allow` just retains references (bounded
by message size).
- **Tradeoff:** loses the loud failure on unexpected additive fields (a
contract-drift signal) — but that brittleness is exactly the bug, and
this matches `main`'s posture.
### `allow` vs `ignore`
- **`allow`** (chosen): tolerates **and exposes** the new fields, so
callers can read `word.start` / `word.end`. Matches `main` (6.x/7.x).
- **`ignore`**: also fixes the crash/mis-typing but **discards** extras
— more conservative (nothing unvalidated retained or re-serialized), at
the cost of the timestamps being unavailable.
Happy to switch to `ignore` if the team prefers minimal surface.
## Notes for reviewers
- `main` (6.x/7.x) already ships `extra="allow"` on the equivalent
model, so this only affects the released **5.3.x** line — hence
targeting `v5`.
- This file is **Fern-generated** (`# auto-generated by Fern from our
API Definition`); if `v5` is regenerated the proper fix belongs in the
API definition. This is a direct patch on the frozen maintenance branch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent 0378dfd commit be07aa6
1 file changed
Lines changed: 2 additions & 2 deletions
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
| 47 | + | |
0 commit comments