Skip to content

fix(tts): use additional_headers for websockets v15 in xAI streaming TTS - #75201

Closed
pluton74mac wants to merge 1 commit into
NousResearch:mainfrom
pluton74mac:fix/xai-tts-websockets-v15-headers
Closed

pluton74mac wants to merge 1 commit into
NousResearch:mainfrom
pluton74mac:fix/xai-tts-websockets-v15-headers

Conversation

@pluton74mac

Copy link
Copy Markdown

Problem

websockets v15.0 renamed the connect() kwarg extra_headers → additional_headers. The xAI streaming TTS path (XAIStreamer._async_frames) was still passing extra_headers, causing every streaming synthesis call to crash with:

BaseEventLoop.create_connection() got an unexpected keyword argument 'extra_headers'

Cascading failure

The crash was worse than "no streaming audio":

  1. Audio still played — the dispatcher fell back to the non-streaming POST endpoint, so responses were still audible. This masked the bug.
  2. Mic jammed — the broken WebSocket's lifecycle left the wake-word detector stuck in a pause → resume loop. After the first spoken reply, the mic felt stuck and wouldn't re-arm cleanly.
  3. Logs flooded — speak-stream synthesis failed warnings on every TTS call.

Timeline from gui.log:

22:08:39 WARNING hermes_cli.web_server: speak-stream synthesis failed: BaseEventLoop.create_connection() got an unexpected keyword argument 'extra_headers'
22:08:56 WARNING hermes_cli.web_server: speak-stream synthesis failed: BaseEventLoop.create_connection() got an unexpected keyword argument 'extra_headers'
... (repeats on every TTS call)
22:11:19 INFO tui_gateway.server: wake.resume: detector resumed=True
22:11:20 INFO tui_gateway.server: wake.pause: detector paused=True   ← stuck loop

Fix

One-line kwarg rename in tools/tts_streaming.py:458:

-            ws_url, extra_headers={"Authorization": f"Bearer {api_key}"}
+            ws_url, additional_headers={"Authorization": f"Bearer {api_key}"}

Verified against the installed websockets version:

>>> import websockets, inspect
>>> [p for p in inspect.signature(websockets.connect).parameters if 'headers' in p]
['additional_headers']

Testing

  • Import check passes: venv/bin/python -c "from tools.tts_streaming import XAIStreamer; print('import OK')"
  • No other extra_headers usage in tts_streaming.py (grep confirmed)
  • Kwarg matches installed websockets v15 API signature
  • E2E: restart gateway, say "hey hermes", confirm streaming TTS plays without speak-stream synthesis failed warnings and mic re-arms cleanly

Notes

  • No existing tests for tts_streaming.py — this is a one-line API-compliance fix, not a behavioral change.
  • The .bak backup file was removed from the staging area; only the one-line fix is committed.

websockets v15.0 renamed the connect() kwarg extra_headers →
additional_headers. The xAI streaming TTS path (XAIStreamer._async_frames)
was still passing extra_headers, causing every streaming synthesis call to
crash with:

  BaseEventLoop.create_connection() got an unexpected keyword argument
  'extra_headers'

The crash had a cascading failure mode: audio fell back to the non-streaming
POST endpoint (so responses were still audible), but the broken WebSocket
lifecycle jammed the wake-word state machine into a pause/resume loop,
making the mic feel stuck after the first spoken reply.

Fix: rename the kwarg to match the websockets v15 API.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the focused compatibility fix. The current implementation still passes extra_headers at tools/tts_streaming.py:458, while the repository pins websockets==15.0.1 in pyproject.toml:102; the proposed additional_headers call matches the current API.

Problems

  • There is no regression test for this call path. tests/tools/test_tts_streaming.py:193-202 covers only xAI credential availability; it does not exercise XAIStreamer._async_frames or inspect websockets.connect arguments.

Suggested changes

  • Add a hermetic mocked WebSocket test that drives XAIStreamer._async_frames and asserts additional_headers={"Authorization": ...} is passed to connect.

This is an automated hermes-sweeper review.

Comment thread tools/tts_streaming.py

async with websockets.connect(
ws_url, extra_headers={"Authorization": f"Bearer {api_key}"}
ws_url, additional_headers={"Authorization": f"Bearer {api_key}"}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add a mocked regression test for XAIStreamer._async_frames that asserts this call passes additional_headers; the existing xAI tests only cover credential availability.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/streaming Streaming responses: gateway delivery, provider wire labels Jul 31, 2026
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists tool/tts Text-to-speech and transcription labels Jul 31, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary

Three PRs address #73985: #75201 fixes only the websockets v15 header keyword, while #73986 rewrites the handshake and wire protocol, restores incremental streaming and failure propagation, enforces the byte cap before enqueueing, and adds loopback regressions; closed #77285 is a substantially overlapping salvage copy of that rewrite.

Related pull requests

Duplicates

#75201 is a narrow subset of #73986, and #77285 is a substantially overlapping salvage duplicate of #73986; #77285 is already closed.

Suggested consolidation

Keep #73986 open as the consolidation target, preserving its complete protocol rewrite, pre-enqueue byte-cap enforcement, failure propagation, and loopback regression suite; its diff addresses the blocking contributor feedback and it is the recorded best fix for #73985. Close #75201 as a duplicate of #73986 despite its earlier keep_open review because #73986 now includes both the same additional_headers change and the requested regression test; leave #77285 closed as a duplicate of #73986.

Cross-PR triage: Reviewed 3 pull requests and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 36 kB of PR diffs, 19 kB of issue/PR text, 7 kB of discussion (10 comments), 3 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@andrexibiza

Copy link
Copy Markdown
Contributor

Verification comment (Vox Lockin lane 10 — adversarial check)

Verified this PR against current origin/main (70db671). Premise CONFIRMED — the fix is still required and correct on main:

  • tools/tts_streaming.py XAIStreamer._async_frames still passes extra_headers={"Authorization": ...} at line ~458.
  • The project pins websockets==15.0.1 (pyproject.toml), and v15's connect() accepts only additional_headers — extra_headers was removed in v14. Every xAI streaming TTS call crashes with TypeError: create_connection() got an unexpected keyword argument 'extra_headers' (then the sync fallback silently downgrades the experience).
  • I verified the venv signature: inspect.signature(websockets.connect) lists additional_headers, no extra_headers.

The 1-line diff (head d44b0a7d62) is correct: additional_headers is the exact kwarg name in websockets 15.0.1. Mergeable (no conflicts), rebaseable.

Status note: mergeable_state shows blocked (no CI checks reported on the branch — likely the first-time-contributor approval gate), so it cannot merge as-is right now.

Composition: since this is a one-line fix in tools/tts_streaming.py (lane 10's file) and the PR is stuck at the CI gate, I included the identical change in my lane's class-completion PR #78234 with credit ("salvaged from #75201, authored by @pluton74mac"). If #75201 gets approved/merged first, #78234 will drop the line — no conflict either way (identical content).

@andrexibiza

Copy link
Copy Markdown
Contributor

Campaign #80424 collision class — XAI-TTS

This PR is part of a collision class adjudicated under the Grok/xAI campaign rebaseline (2026-08-14). The canonical disposition is recorded on #80424.

Implementation of this class is blocked until Wave 0 (T00-T04) completes classification and the canonical collision/supersession table is agreed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/streaming Streaming responses: gateway delivery, provider wire P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants