You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* rename to Speech Engine
* veng -> seng
* initial commit
* Bug fixes and API surface updates
* bug fixes and QoL improvements
* Address comments
* Fix client_wrapper naming and use public API for key access
Rename client_options parameter to client_wrapper for consistency with
Fern SDK conventions. Use get_headers() instead of accessing private
_api_key attribute. Update tests to match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix send_response silently failing when event_id is absent
Use a dedicated _in_transcript_handler flag instead of checking
_current_event_id is None, which conflated "no handler running"
with "handler running but transcript had no event_id".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix crash when user_transcript is null in wire message
Move the log call after the try/except and use `or []` to handle
null transcript_data, preventing len(None) TypeError.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Bump websockets minimum to >=13.0
The server uses websockets 13.x API (websocket.request.path,
websocket.request.headers) which doesn't exist in 11.x-12.x.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Set _closed on protocol close message to stop the receive loop
Without this, the run() loop continues calling recv() after a
close message, processing stale messages and reporting is_open
as True.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Move auth to process_request for pre-handshake rejection
Use websockets process_request callback to verify JWT before the
WebSocket handshake completes. Unauthenticated requests now get a
plain HTTP 401 instead of completing the upgrade first.
Also include exception in logger.exception error handler message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Guard against non-dict JSON messages in the receive loop
json.loads can return lists, strings, numbers, or null. These
would crash _handle_message which expects a dict. Now emits an
error event and continues instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Capture event_id upfront in send_response for string path
The string response path called _send_agent_response twice without
an explicit event_id, reading self._current_event_id at each call.
An interruption between the two awaits could stamp the terminator
with the wrong event_id. Now captures event_id once and passes it
through, matching the stream path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire speech engine to Fern-generated CRUD client
Replace the stub _AsyncSpeechEngineAccessor with custom
SpeechEngineClient/AsyncSpeechEngineClient classes that extend the
Fern-generated clients and add resource() for WebSocket server setup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* override get
* fixes
* Return resource instead of response
* fix bugs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Speech Engine lets you build server-side voice agents that receive real-time transcripts from the ElevenLabs API and stream LLM responses back for text-to-speech synthesis. Your server acts as a WebSocket endpoint — ElevenLabs connects to it, sends user transcripts, and your code decides how to respond.
272
+
273
+
Speech Engine is async-only and available on `AsyncElevenLabs`.
When a new transcript arrives while a previous response is still streaming, the previous handler's `asyncio.Task` is cancelled automatically. Any `await` in your handler (including LLM SDK calls) will raise `asyncio.CancelledError`, which cleanly aborts the in-flight request. No manual signal handling needed.
355
+
356
+
### Custom Server Integration (FastAPI, Starlette)
357
+
358
+
For integrating with an existing web server, use `create_session()` instead of `serve()`:
359
+
360
+
```python
361
+
from fastapi import FastAPI, WebSocket
362
+
363
+
app = FastAPI()
364
+
engine =...# SpeechEngineResource from await client.speech_engine.get(...)
365
+
366
+
@app.websocket("/api/speech-engine/ws")
367
+
asyncdefspeech_engine_ws(ws: WebSocket):
368
+
await ws.accept()
369
+
session = engine.create_session(ws, debug=True)
370
+
session.on("user_transcript", handle_transcript)
371
+
await session.run()
372
+
```
373
+
374
+
When using `session.on()` directly, handlers receive just the event data (no `session` argument, since you already have the reference):
0 commit comments