Skip to content

fix(websockets): use is_ws_alive across modern websockets>=12 API#113

Merged
nicolotognoni merged 1 commit into
PatterAI:mainfrom
knowsuchagency:fix/websockets-ga-closed-attr
May 27, 2026
Merged

fix(websockets): use is_ws_alive across modern websockets>=12 API#113
nicolotognoni merged 1 commit into
PatterAI:mainfrom
knowsuchagency:fix/websockets-ga-closed-attr

Conversation

@knowsuchagency
Copy link
Copy Markdown
Contributor

Summary

  • Promote the existing _is_parked_ws_alive helper out of stream_handler.py into libraries/python/getpatter/utils/ws.py as is_ws_alive, and re-use it at every WS-liveness check that goes through the websockets library.
  • Fixes AttributeError: 'ClientConnection' object has no attribute 'closed' that crashed pipeline-mode calls immediately on stream start (Patter pins websockets>=14,<16, which dropped .closed in v12).
  • 8 new unit tests in tests/test_utils_ws.py cover modern (state, close_code), legacy (closed), and unknown-shape paths.

Closes #111.

Why

Two parked-WS-adoption sites still relied on ws.closed:

  • stream_handler.py:2192 (TTS WS adoption)
  • stream_handler.py:2230 (STT WS adoption)
  • providers/elevenlabs_ws_tts.py:453 (parked WS pickup in synthesize)

The websockets library removed .closed in v12, but pyproject.toml pins websockets>=14,<16. The result is that every pipeline-mode call crashes immediately on stream start:

[patter] ERROR getpatter: Stream error: 'ClientConnection' object has no attribute 'closed'
  File "libraries/python/getpatter/stream_handler.py", line 2192
  ws_alive = parked_tts.ws is not None and not parked_tts.ws.closed
AttributeError: 'ClientConnection' object has no attribute 'closed'. Did you mean: 'close'?

The existing _is_parked_ws_alive helper already handles both APIs cleanly; it just wasn't reused everywhere.

Out of scope

The aiohttp-backed STT providers (assemblyai_stt, cartesia_stt, soniox_stt) still call .closed because aiohttp.ClientWebSocketResponse.closed remains valid. They are not affected by this bug. Docstring comments in openai_realtime.py:465 and cartesia_stt.py:307 still mention not ws.closed as a precondition — left as a follow-up since the code there is correct.

Test plan

  • pytest libraries/python/tests/test_utils_ws.py -v — 8/8 pass
  • Verified locally that pipeline-mode call with ElevenLabsWebSocketTTS (via the getpatter[local] editable install) no longer raises AttributeError on stream start
  • CI green

Two parked-WS-liveness checks in the SDK still used `ws.closed`, removed
from the `websockets` library in v12. Patter pins `websockets>=14,<16`
in pyproject.toml, so calls crashed immediately with:

  AttributeError: 'ClientConnection' object has no attribute 'closed'

Promote the existing `_is_parked_ws_alive` helper out of stream_handler
into `getpatter/utils/ws.py` as `is_ws_alive`, and use it at every WS
liveness check that goes through the `websockets` library:

  - stream_handler.py:2192 (TTS WS adoption)
  - stream_handler.py:2230 (STT WS adoption)
  - providers/elevenlabs_ws_tts.py:453 (parked WS pickup in synthesize)

aiohttp-backed providers (assemblyai_stt, cartesia_stt, soniox_stt)
still call `.closed` because `aiohttp.ClientWebSocketResponse.closed`
remains a valid property — they are unaffected.

Adds 8 unit tests covering modern (`state`, `close_code`) and legacy
(`closed`) shapes, plus the fail-closed default for unknown shapes.

Closes PatterAI#111.
@nicolotognoni nicolotognoni merged commit ed49c6b into PatterAI:main May 27, 2026
9 checks passed
nicolotognoni added a commit that referenced this pull request May 27, 2026
…115) (#118)

- Pipeline mode in `libraries/typescript/src/stream-handler.ts` did not
  inject the built-in `transfer_call` / `end_call` tools into the
  `LLMLoop` (both `new LLMLoop(...)` call sites at lines 1891 and 1906
  passed `agent.tools` through unchanged). The Realtime path injects
  them at `server.ts:374`; pipeline was missing the parity. Added
  `augmentWithBuiltinHandoffTools` helper mirroring the Python helper
  shipped in #115; it builds handler closures that validate E.164 /
  default `reason` and dispatch to the existing telephony bridge
  methods (`bridge.transferCall` / `bridge.endCall`). Built-ins are
  skipped when the corresponding callback is missing, keeping
  non-telephony test harnesses clean. Exported
  `TRANSFER_CALL_TOOL` / `END_CALL_TOOL` from `server.ts` so the
  helper can re-use the canonical schema.
- Docs: `docs/typescript-sdk/events.mdx` advertised the same
  non-existent `phone.events.on(PatterEventType.X, handler)` API as
  the Python events page (closed by #114). Replaced the broken
  `EventBus` section with documentation of the APIs that actually
  exist on the TypeScript `Patter` class: speech-edge attribute
  setters (`onUserSpeechStarted` / `onAgentSpeechEnded` /
  `onLlmToken` / `onAudioOut` etc.) and tool events via
  `onTranscript` with `role === "tool"`.
- CHANGELOG: backfilled `## Unreleased` `### Fixed` entries for
  upstream PRs #113, #114, #115 (Python fixes by @knowsuchagency that
  landed without the corresponding TypeScript / changelog updates),
  plus entries for the two TypeScript parity fixes shipped here.

Test plan:
- Python: covered by tests added in the upstream PRs (#113, #115).
- TypeScript: 8 new unit tests in
  `libraries/typescript/tests/pipeline-builtin-tools.test.ts` cover
  empty-user-tools, non-empty user tools order, missing-callback
  skip, transfer dispatch + E.164 rejection, end_call default and
  user-supplied `reason`. Full suite: 1521 passed / 8 skipped, lint
  clean, build green.
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.

[BUG] AttributeError: 'ClientConnection' object has no attribute 'closed' on parked WS adoption (websockets>=12 API break)

2 participants