fix: emit repeated tag query params for multi-value speak v2 streaming#85
Merged
Merged
Conversation
Follow-up to #82. The TTS speak v2 streaming client mangles a multi-value tag the same way the listen clients did before #82: it serializes the tag union with String.valueOf, so a multi-value list collapses into a single stringified param instead of repeated params. Same class of bug, same root cause -- the generated WebSocket template stringifies array query params instead of using the shared array-aware serializer the REST path uses. Route tag through QueryStringMapper with repeats enabled, matching the listen fix and the REST path. tag is the only array-valued query param on speak v2. Scalar strings are unchanged; an empty list now omits the param. The speak v2 client is already frozen in .fernignore; comments updated so both the dispatcher patch and this multi-value fix are documented against it. The durable fix belongs upstream in the Fern generator WebSocket template, so listen and speak get cured together.
GregHolmes
requested review from
deepgram-kiley and
dg-coreylweathers
as code owners
July 24, 2026 09:49
dg-coreylweathers
approved these changes
Jul 24, 2026
dg-coreylweathers
left a comment
Contributor
There was a problem hiding this comment.
Verified: correct tag double-unwrap, QueryStringMapper handles String/List/empty, wire tests pass, gate green in Docker. Merge this before #86.
GregHolmes
added a commit
that referenced
this pull request
Jul 24, 2026
) ## Summary Fixes #83. On the streaming connect builders, the upgrade URL is built only from the typed options and `additionalProperties` is never emitted. So unmodeled query params set via the builder escape hatch -- for example `no_delay` -- were silently dropped: - Before: setting `no_delay` via `additionalProperty` yielded `model=nova-3` only - After: yields `model=nova-3&no_delay=true` `connect` also accepts no `RequestOptions`, and `RequestOptions.queryParameters` is the REST world's mechanism for arbitrary query params -- so before this change there was no working way to send an unmodeled query param on a streaming connection at all. ## Root cause A generator gap, not a spec issue. The WebSocket connect template serializes only the typed options and ignores `additionalProperties`. The builder still exposes `additionalProperty`, so the escape hatch looks available but does nothing on the wire. ## Fix Emit `additionalProperties` as query params in `connect`, routed through `QueryStringMapper` with `arraysAsRepeats` enabled, so boolean, numeric, and list values serialize exactly as the REST path does. `ConnectOptions` is request-only and never deserialized, so the map only ever holds what the caller set via the builder. Applied to all four connect clients: listen v1, listen v2, speak v1, speak v2. speak v1 was not previously frozen and is added to `.fernignore`. ## Tests New `StreamingAdditionalPropertiesWireTest` with MockWebServer coverage on all four clients: a `no_delay` boolean plus a custom string reach the captured upgrade URL and coexist with the typed `model` param. Also verified with a throwaway showing boolean, numeric, and list-valued escape-hatch params all serialize correctly -- `multi=x&multi=y` for a list. `./gradlew spotlessJavaCheck test` is green. ## Upstream / follow-ups - Generator: the WebSocket connect template should emit `additionalProperties` on the connect path so all four clients can be un-frozen. Added to the Fern-upgrade defect list. - Spec: `no_delay` is a real listen param and arguably should be modeled as a typed option, which would remove the need for the escape hatch for that param. Note: overlaps with #85 in speak v2 `connect` -- both touch the same method at different points, so whichever merges second needs a trivial rebase.
GregHolmes
pushed a commit
that referenced
this pull request
Jul 24, 2026
🤖 I have created a release *beep* *boop* --- ## [0.7.1](v0.7.0...v0.7.1) (2026-07-24) ### Bug Fixes * emit additionalProperties as query params on streaming connect ([#86](#86)) ([28229fc](28229fc)) * emit repeated query params for all multi-value streaming options ([#82](#82)) ([746be15](746be15)), closes [#77](#77) * emit repeated tag query params for multi-value speak v2 streaming ([#85](#85)) ([e748619](e748619)) #### What's in this release Three related fixes to streaming (WebSocket) query-parameter serialization on `listen` and `speak`. All are bug fixes — no API changes. **Multi-keyterm streaming fix** - Streaming connections mis-serialized a multi-value `keyterm` (and other array-valued params): a `List<String>` was stringified into a single param (`keyterm=[a, b]`) instead of repeated params (`keyterm=a&keyterm=b`). The server treats the stringified list as one nonsense term, so multiple key terms were not boosted — recognition of them was actually degraded. - Now fixed for every array-valued streaming query param — `listen`: `keyterm`, `keywords`, `replace`, `search`, `tag`, `extra`, `language_hint`; `speak`: `tag` — by routing them through the same repeated-param serialization the REST path already uses (#81, #82, #85; closes #77). - **If you worked around this by hand-joining terms into a single string, drop that workaround** — pass a real `List<String>` (e.g. `ListenV1Keyterm.of(List.of("a", "b"))`) and each term is now sent as its own `keyterm=` param. **Unmodeled streaming query params now work (e.g. `no_delay`)** - The streaming `connect(...)` builders dropped `additionalProperties`, so unmodeled params set via `.additionalProperty("no_delay", true)` never reached the URL — it was impossible to set `no_delay` on a streaming connection. - Now fixed on all four connect clients (`listen` v1/v2, `speak` v1/v2): `additionalProperties` are emitted as query params, so any not-yet-modeled param can be passed through until it gains a typed option (#86; closes #83). _All four streaming clients remain frozen in `.fernignore`; the durable fix belongs in the Fern generator's WebSocket template, tracked for the next generator upgrade._ --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.
Summary
Follow-up to #82, extending the same fix to TTS. The speak v2 streaming client mangles a multi-value
tagexactly as the listen clients did before #82: it serializes the tag union by stringifying the value, so a multi-value list collapses into a single param instead of repeated params:tag=%5Ba%2C+b%5D— one param, value "[a, b]"tag=a&tag=b— one param per valueRoot cause
Same class as the listen fix. The Fern generator hand-rolls the streaming query-building path and stringifies array params, while the REST path routes through the shared array-aware serializer and is correct. This is the same generator defect in the WebSocket client template.
Fix
Route
tagthrough the shared array-aware serializer with repeats enabled, matching the listen fix and the REST path.tagis the only array-valued query param on speak v2. Scalar strings are unchanged; an empty list now omits the param.Freeze / regen
The speak v2 client is already frozen in
.fernignore; comments updated so both the dispatcher patch and this multi-value fix are documented against it. The durable fix belongs upstream in the Fern generator WebSocket template, so listen and speak get cured together.Tests
New connect-handshake wire coverage with MockWebServer, asserting the captured
/v2/speakupgrade URL: a multi-value list becomes repeated params, a single string stays one param. Also verified with a throwaway matrix across four shapes -- multi-list, single-list, scalar, empty -- all passing../gradlew spotlessJavaCheck testis green.Credit to Corey for spotting this in the #82 review.