Skip to content

fix: emit repeated tag query params for multi-value speak v2 streaming#85

Merged
GregHolmes merged 1 commit into
mainfrom
fix/speak-v2-streaming-tag-serialization
Jul 24, 2026
Merged

fix: emit repeated tag query params for multi-value speak v2 streaming#85
GregHolmes merged 1 commit into
mainfrom
fix/speak-v2-streaming-tag-serialization

Conversation

@GregHolmes

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #82, extending the same fix to TTS. The speak v2 streaming client mangles a multi-value tag exactly 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:

  • Wrong: tag=%5Ba%2C+b%5D — one param, value "[a, b]"
  • Right: tag=a&tag=b — one param per value

Root 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 tag through the shared array-aware serializer 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.

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/speak upgrade 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 test is green.

Credit to Corey for spotting this in the #82 review.

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.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: correct tag double-unwrap, QueryStringMapper handles String/List/empty, wire tests pass, gate green in Docker. Merge this before #86.

@GregHolmes
GregHolmes merged commit e748619 into main Jul 24, 2026
8 checks passed
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>
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.

2 participants