Skip to content

Commit a65d1aa

Browse files
committed
fix(agent): make UpdateListen provider shim a merged shape, not a union
The union-based shim fixed construction and `model` reads but left a third regression: property access on a union requires the property on every arm, so reads of the V2-only tuning fields (eot_threshold, eager_eot_threshold, eot_timeout_ms, language_hints) failed with TS2339 because the generated union's V1 arm lacks them. No union can fix that. Model the field as a single merged shape instead: Omit<DeepgramListenProviderV2, 'version'> (keeping every previously-readable field readable and `model` required) with `version` widened to string for the new v1/v2 selection, plus the two V1-only fields as optional additions. Deriving from DeepgramListenProviderV2 means future V2 field changes flow through automatically. Verified against a main worktree: all three pre-regen patterns compile and the new explicit v1 selection also works, so the field is now a strict superset of main. Extends the regression coverage to all three break modes.
1 parent a6e3c79 commit a65d1aa

2 files changed

Lines changed: 78 additions & 29 deletions

File tree

src/api/resources/agent/resources/v1/types/AgentV1UpdateListen.ts

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,49 @@ export namespace AgentV1UpdateListen {
1414
* Listen configuration to update. Contains a provider object with the same schema as Settings. The model and language can be changed mid-session.
1515
*/
1616
export interface Listen {
17-
/**
18-
* Backward-compat shim: the 2026-07-31 regen repointed this field from
19-
* `DeepgramListenProviderV2` to the new `AgentV1UpdateListenListenProvider`
20-
* union, whose `V1`/`V2` variants both redeclare the `version` discriminant as
21-
* REQUIRED. `DeepgramListenProviderV2.version` is optional, so pre-existing
22-
* callers that omitted it — `{ type: "deepgram", model: "flux-general-en" }` —
23-
* stopped compiling (TS2322).
24-
*
25-
* We union the bare `DeepgramListenProviderV2` back in so those call sites keep
26-
* working, while the generated union still offers the new explicit v1/v2
27-
* selection. Compile-compat only: the serialized payload is unchanged, and the
28-
* spec actually RELAXED this field (its own docs dropped "the provider identity
29-
* (type, version, model) is required and must match the current session"), so
30-
* requiring `version` looks like a generator modeling artifact rather than
31-
* intent. Drop this shim if the generator stops requiring the discriminant.
32-
*
33-
* The `& { model: string }` intersection pins the second half of the same
34-
* regression: the generated union's `V1` arm declares `model?`, which widened
35-
* reads of `provider.model` from `string` to `string | undefined`. `model` was
36-
* required here before the regen, so re-requiring it restores the previous
37-
* contract on both construction and reads, and only tightens the brand-new
38-
* (never-released) v1 arm.
39-
*
40-
* Regression coverage in tests/unit/regen-constraints.test.ts (also gated by
41-
* `make typecheck-tests`).
42-
*/
43-
provider: (Deepgram.agent.AgentV1UpdateListenListenProvider | Deepgram.DeepgramListenProviderV2) & {
44-
model: string;
45-
};
17+
provider: AgentV1UpdateListen.Provider;
4618
}
19+
20+
/**
21+
* Backward-compat shim for `AgentV1UpdateListen.Listen.provider`.
22+
*
23+
* Before the 2026-07-31 regen this field was plain `DeepgramListenProviderV2`. The
24+
* regen repointed it at the new `AgentV1UpdateListenListenProvider` union
25+
* (`V1 | V2`), which broke three things at once for existing callers:
26+
*
27+
* 1. Both union arms redeclare the `version` discriminant as REQUIRED, while
28+
* `DeepgramListenProviderV2.version` is optional — so
29+
* `{ type: "deepgram", model: "flux-general-en" }` stopped compiling (TS2322).
30+
* 2. The `V1` arm declares `model?`, widening reads of `provider.model` from
31+
* `string` to `string | undefined`.
32+
* 3. Property access on a union requires the property on EVERY arm, so reads of
33+
* the V2-only fields (`eot_threshold`, `eager_eot_threshold`, `eot_timeout_ms`,
34+
* `language_hints`) started failing with TS2339 — the `V1` arm lacks them.
35+
*
36+
* No union can fix (3), so we model the field as a single merged shape instead:
37+
* the V2 provider (which keeps every previously-readable field readable, and keeps
38+
* `model` required) with `version` widened to `string` for the new v1/v2 selection,
39+
* plus the two V1-only fields as optional additions. Deriving from
40+
* `DeepgramListenProviderV2` means future V2 field changes flow through
41+
* automatically; only the V1-only extras are hand-listed.
42+
*
43+
* Compile-compat only — the serialized payload is unchanged. The trade-off is that
44+
* nonsensical mixtures (`version: "v1"` alongside `eot_threshold`) type-check; the
45+
* API ignores what does not apply. Note the spec itself RELAXED this field (its docs
46+
* dropped "the provider identity (type, version, model) is required and must match
47+
* the current session"), so the required discriminant looks like a Fern modeling
48+
* artifact — replace this shim with the generated union once the generator stops
49+
* requiring `version`.
50+
*
51+
* Regression coverage in tests/unit/regen-constraints.test.ts (also gated by
52+
* `make typecheck-tests`).
53+
*/
54+
export type Provider = Omit<Deepgram.DeepgramListenProviderV2, "version"> & {
55+
/** `v1` selects the legacy Listen API; `v2` (the default) selects Flux. Open per the spec. */
56+
version?: string | undefined;
57+
/** v1 only: language code to switch to mid-session. */
58+
language?: string | undefined;
59+
/** v1 only: applies smart formatting to improve transcript readability. */
60+
smart_format?: boolean | undefined;
61+
};
4762
}

tests/unit/regen-constraints.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,40 @@ describe("2026-07-31 regen constraints", () => {
223223
expect(v1.listen.provider.model).toBe("nova-3");
224224
expect(v2.listen.provider.model).toBe("flux-general-en");
225225
});
226+
227+
it("keeps `model` reading as a required string (not string | undefined)", () => {
228+
const msg: Deepgram.agent.AgentV1UpdateListen = {
229+
type: "UpdateListen",
230+
listen: { provider: { type: "deepgram", model: "flux-general-en" } },
231+
};
232+
// Compile-time assertion: the V1 arm of the generated union declares
233+
// `model?`, which would widen this to `string | undefined`.
234+
const model: string = msg.listen.provider.model;
235+
expect(model).toBe("flux-general-en");
236+
});
237+
238+
it("keeps the V2-only tuning fields READABLE off provider", () => {
239+
// Property access on a union requires the property on every arm, so these
240+
// reads broke with TS2339 once the V1 arm (which lacks them) was introduced.
241+
const msg: Deepgram.agent.AgentV1UpdateListen = {
242+
type: "UpdateListen",
243+
listen: {
244+
provider: {
245+
type: "deepgram",
246+
model: "flux-general-multi",
247+
eot_threshold: 0.7,
248+
eager_eot_threshold: 0.4,
249+
eot_timeout_ms: 4000,
250+
language_hints: ["en", "es"],
251+
keyterms: ["Deepgram"],
252+
},
253+
},
254+
};
255+
const p = msg.listen.provider;
256+
expect([p.eot_threshold, p.eager_eot_threshold, p.eot_timeout_ms]).toEqual([0.7, 0.4, 4000]);
257+
expect(p.language_hints).toEqual(["en", "es"]);
258+
expect(p.keyterms).toEqual(["Deepgram"]);
259+
});
226260
});
227261

228262
describe("listen v2 ForceEndTurn", () => {

0 commit comments

Comments
 (0)