#859·fonoster

[Feature] Allow per-call voice override in the Say verb via options

Author: psandersCreated Jun 23, 2026Updated Jun 23, 2026
Labelsenhancementvoice

Summary

Let an external Voice app override the configured TTS voice on a per-utterance basis by passing a voice through say(text, options).

Problem / Motivation

The say() verb already accepts an options argument (SayOptions) that is faithfully carried end-to-end: VoiceResponse.saySay verb → SayRequest.options (protobuf Struct) → createSayHandlervoiceClient.synthesize(text, options)SpeechHandlertts.synthesize(text, options). The engine-facing type even declares a voice slot (SynthOptions = { voice: string }).

But no TTS engine reads it. Google.ts and ElevenLabs.ts resolve the voice exclusively from this.engineConfig.config.voice and use options only for logging. So today there is no way to change the voice per call — and passing { voice } silently does nothing, which is a bug-like trap given the type advertises support.

Proposed Solution

  • In each TTS engine (Google, ElevenLabs, Azure, Deepgram), resolve the voice as options.voice ?? this.engineConfig.config.voice.
  • Add an optional voice field to the Say verb's Zod validation schema (mods/voice/src/verbs/Say.ts) so it isn't dropped at validation.
  • Document the option on VoiceResponse.say and consider validating the voice value per engine.

Acceptance Criteria

  • response.say("Hello", { voice: "<engine voice>" }) uses the supplied voice
  • Omitting voice falls back to the app's configured TTS voice (no behavior change)
  • Supported across Google, ElevenLabs, Azure, and Deepgram engines
  • Say verb schema accepts an optional voice
  • Invalid/unknown voice values are handled gracefully (clear error or documented fallback)

Notes / Architectural Consideration

This may be a good checkpoint to evaluate moving the TTS and STT engines out of apiserver and into the voice module, removing them from the apiserver. Worth weighing the ownership/coupling of the speech engines as part of (or right before) this work.

Priority

P2