Skip to content

SMOODEV-2534: voice input/output — mic capture + TTS playback over the browser-voice WS (flag OFF)#31

Merged
brentrager merged 1 commit into
mainfrom
SMOODEV-2534-voice
Jul 12, 2026
Merged

SMOODEV-2534: voice input/output — mic capture + TTS playback over the browser-voice WS (flag OFF)#31
brentrager merged 1 commit into
mainfrom
SMOODEV-2534-voice

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Jira: SMOODEV-2534

Adds premium browser voice to the widget (ADR-084), shipped dark behind voice: { enabled: false, url? } — OFF by default, zero voice UI when off. Live integration is blocked on the server-side browser-voice WS endpoint PR in the monorepo; everything here is built and tested against the frozen protocol with a mock WS.

Protocol (frozen)

Endpoint wss://twilio-voice.smoo.ai/browser-voice/ws (overridable via voice.url).

  • client → server: first frame {"type":"start","agent_id","conversation_id?","token?"} (public agents auth by the browser-sent Origin header), then binary PCM linear16 mono @ 16 kHz mic chunks, {"type":"interrupt"}, {"type":"stop"}.
  • server → client: transcript_partial / transcript_final / reply_text / speaking_started / binary 16 kHz TTS PCM / speaking_done / handoff / error {code}.

What's here

  • src/voice-session.ts — framework-free VoiceSession: WS lifecycle, mic capture (getUserMedia → AudioWorklet with ScriptProcessor fallback → bucket-average downsample to 16 kHz Int16), gapless playback (PcmPlayer schedules chunks back-to-back through a 16 kHz AudioContext), and events out. Browser audio + WS are thin injectable seams so tests run in jsdom. No new runtime deps.
  • Widget integration — Aurora Glass mic toggle in the composer (ghost twin of the send button; pulsing ring while listening, secondary-accent shimmer while the agent speaks), live partial transcript in the input area, and voice turns rendered through the existing message-append path (transcript_final → user bubble, reply_text → assistant bubble) so history stays coherent with text chat.
  • Thread continuity — the text session's conversationId (now captured from create/resume) rides the start frame as conversation_id, so voice resumes the same thread.

Barge-in

While the agent's TTS is playing, any mic frame whose RMS exceeds a small threshold (default 0.02, post-AGC) sends {"type":"interrupt"} and flushes the playback queue immediately; the barge-in speech itself keeps streaming so the server transcribes it. Hitting the mic button mid-playback interrupts + flushes the same way before ending the session.

Follow-up

The frozen protocol never returns the voice-created conversation id to the client, so a fresh voice-first session can't be adopted for subsequent text turns — needs a server-side protocol addition if we want it.

Verification

  • pnpm test — 167 passed (12 files), incl. new coverage: start-frame framing, binary passthrough + drop-until-open, barge-in (loud/quiet/manual), downsampler correctness on a synthetic 48 kHz sine (+44.1 kHz ratio, clamping), playback ordering/re-anchor/flush, server-event fan-out, teardown idempotence, and config gating (no mic button unless enabled).
  • pnpm typecheck, pnpm typecheck:e2e, pnpm build — green.
  • Changeset: minor.

🤖 Generated with Claude Code

…e browser-voice WS (flag OFF)

Adds a framework-free VoiceSession speaking the frozen browser-voice
protocol (start/interrupt/stop JSON + 16 kHz linear16 binary both ways),
a composer mic toggle gated by the new voice.enabled config (OFF by
default, zero UI when off), RMS barge-in that interrupts + flushes
playback, and voice turns rendered through the existing message path so
the transcript stays coherent with text chat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5a449f8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@smooai/chat-widget Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brentrager brentrager merged commit 1295007 into main Jul 12, 2026
1 check passed
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.

1 participant