Skip to content

Commit 6242787

Browse files
committed
initial commit
1 parent 522265d commit 6242787

8 files changed

Lines changed: 1743 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""ElevenLabs Speech Engine SDK module."""
2+
3+
from .resource import SpeechEngineResource
4+
from .server import SpeechEngineServer
5+
from .session import SpeechEngineSession
6+
from .types import (
7+
CLOSE,
8+
DISCONNECTED,
9+
ERROR,
10+
INIT,
11+
USER_TRANSCRIPT,
12+
ConversationMessage,
13+
WebSocketLike,
14+
)
15+
16+
__all__ = [
17+
"ConversationMessage",
18+
"SpeechEngineResource",
19+
"SpeechEngineServer",
20+
"SpeechEngineSession",
21+
"WebSocketLike",
22+
"CLOSE",
23+
"DISCONNECTED",
24+
"ERROR",
25+
"INIT",
26+
"USER_TRANSCRIPT",
27+
]
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""SpeechEngineResource — client-facing handle for a speech engine instance."""
2+
3+
import typing
4+
5+
from .server import SpeechEngineServer
6+
from .session import SpeechEngineSession
7+
from .types import WebSocketLike
8+
9+
10+
class SpeechEngineResource:
11+
"""Represents a speech engine instance.
12+
13+
Returned by ``await client.speech_engine.get("seng_123")``.
14+
15+
Use :meth:`serve` to start a standalone WebSocket server, or
16+
:meth:`create_session` to wrap an existing WebSocket for custom
17+
server integration (FastAPI, Starlette, etc.).
18+
19+
Example::
20+
21+
engine = await elevenlabs.speech_engine.get("seng_123")
22+
23+
async def on_transcript(transcript, session):
24+
stream = await openai.responses.create(...)
25+
await session.send_response(stream)
26+
27+
await engine.serve(
28+
port=3001,
29+
debug=True,
30+
on_transcript=on_transcript,
31+
)
32+
"""
33+
34+
def __init__(
35+
self,
36+
engine_id: str,
37+
client_options: typing.Any = None,
38+
) -> None:
39+
self.engine_id = engine_id
40+
self._options = client_options
41+
42+
async def serve(
43+
self,
44+
*,
45+
port: int = 3001,
46+
debug: bool = False,
47+
**handlers: typing.Any,
48+
) -> None:
49+
"""Start a standalone WebSocket server. Blocks until stopped."""
50+
server = SpeechEngineServer(port=port, debug=debug, **handlers)
51+
await server.serve()
52+
53+
def create_session(
54+
self,
55+
ws: WebSocketLike,
56+
*,
57+
debug: bool = False,
58+
) -> SpeechEngineSession:
59+
"""Wrap *ws* in a :class:`SpeechEngineSession`.
60+
61+
Use this for custom server integration (e.g. FastAPI, Starlette).
62+
Wire handlers via :meth:`~SpeechEngineSession.on` then ``await
63+
session.run()``.
64+
"""
65+
return SpeechEngineSession(ws, debug=debug)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""SpeechEngineServer — standalone WebSocket server for Speech Engine."""
2+
3+
import asyncio
4+
import logging
5+
import typing
6+
7+
from .session import SpeechEngineSession, _wire_handlers
8+
from .types import WebSocketLike
9+
10+
logger = logging.getLogger("elevenlabs.speech_engine")
11+
12+
13+
class SpeechEngineServer:
14+
"""Standalone WebSocket server that produces :class:`SpeechEngineSession`
15+
instances for each incoming connection from the ElevenLabs Speech Engine
16+
API.
17+
18+
Example::
19+
20+
server = SpeechEngineServer(
21+
port=3001,
22+
debug=True,
23+
on_transcript=handle_transcript,
24+
)
25+
await server.serve()
26+
"""
27+
28+
def __init__(
29+
self,
30+
*,
31+
port: int = 3001,
32+
debug: bool = False,
33+
**handlers: typing.Any,
34+
) -> None:
35+
self._port = port
36+
self._debug = debug
37+
self._handlers = handlers
38+
self._stop_event = asyncio.Event()
39+
self._server = None # type: typing.Any
40+
41+
def handle_connection(self, ws: WebSocketLike) -> SpeechEngineSession:
42+
"""Wrap *ws* in a :class:`SpeechEngineSession` with the server's
43+
handlers wired up.
44+
45+
Use this when you manage your own WebSocket server and want to wrap
46+
individual connections. The returned session's :meth:`run` must
47+
still be awaited by the caller.
48+
"""
49+
session = SpeechEngineSession(ws, debug=self._debug)
50+
_wire_handlers(session, self._handlers)
51+
return session
52+
53+
async def serve(self) -> None:
54+
"""Start the WebSocket server. Blocks until :meth:`stop` is called."""
55+
import websockets # noqa: E402 — keep import lazy
56+
57+
async def _handler(websocket: typing.Any, *_args: typing.Any) -> None:
58+
session = self.handle_connection(websocket)
59+
await session.run()
60+
61+
self._server = await websockets.serve( # type: ignore[attr-defined]
62+
_handler,
63+
"",
64+
self._port,
65+
)
66+
logger.debug("speech engine server listening on port %d", self._port)
67+
try:
68+
await self._stop_event.wait()
69+
finally:
70+
self._server.close()
71+
await self._server.wait_closed()
72+
73+
async def stop(self) -> None:
74+
"""Signal the server to shut down gracefully."""
75+
self._stop_event.set()

0 commit comments

Comments
 (0)