Metadata-Version: 2.5
Name: livekit-plugins-pocket
Version: 0.1.2
Summary: Third-party LiveKit Agents TTS plugin for a self-hosted Pocket TTS websocket server.
Project-URL: Documentation, https://docs.livekit.io/agents/models/tts/
Project-URL: TTS server, https://github.com/Thanush101/livekit-streaming-tts
Author: Thanush
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: livekit,pocket-tts,tts,voice,websocket
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Requires-Python: <3.15,>=3.10
Requires-Dist: aiohttp~=3.10
Requires-Dist: anyio>=4.15.1
Requires-Dist: httpx2>=2.16.0
Requires-Dist: jiter>=0.18.0
Requires-Dist: livekit-agents>=1.8.9
Requires-Dist: livekit-api>=1.2.2
Requires-Dist: livekit-blingfire>=1.2.0
Requires-Dist: openai>=3.37.0
Requires-Dist: opentelemetry-api>=1.45.1
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.45.1
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.45.1
Requires-Dist: opentelemetry-exporter-otlp>=1.45.1
Requires-Dist: opentelemetry-sdk>=1.45.1
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# livekit-plugins-pocket

Third-party [LiveKit Agents](https://docs.livekit.io/agents/) TTS plugin for a self-hosted [Pocket TTS](https://github.com/kyutai-labs/pocket-tts) server. It speaks the WebSocket protocol implemented by [livekit-streaming-tts](https://github.com/Thanush101/livekit-streaming-tts).

This package is not part of [livekit/agents](https://github.com/livekit/agents). LiveKit's maintainers do not publish or support it. The import matches the official plugin layout so it can sit next to `livekit-plugins-cartesia` and the rest:

```python
from livekit.plugins.pocket import TTS
```

The wheel does not ship `livekit/__init__.py` or `livekit/plugins/__init__.py`, so it does not replace the `livekit` realtime package or any other plugin. The distribution name `livekit-plugins-pocket` follows the official `livekit-plugins-<name>` pattern. Check PyPI before publishing it; LiveKit owns that naming convention for in-repo plugins.

## Install

Python 3.10–3.14, same range as current `livekit-agents`.

```bash
pip install -e ./livekit-plugins-pocket
```

That pulls `livekit-agents>=1.8.9`. It does not download Pocket weights.

## Run the server first

The plugin is only a client. Synthesis happens on the server (Pocket, 24 kHz). The websocket sends int16 PCM as each chunk is decoded. `POST /v1/tts` still returns one clip:

```bash
# from the livekit-streaming-tts checkout, with the pocket extra installed
tts-server --engine pocket --port 8001
curl http://127.0.0.1:8001/health
```

That command loads Kyutai's English weights. A community model is selected on the server, not in this plugin. See Community models in the livekit-streaming-tts README. For the multilingual finetune:

```bash
POCKET_TTS_MODEL=Thanushttk/pocket-tts-multilingual tts-server --engine pocket --port 8001
```

The plugin does not send a model id. Weights stay on the server process that just started. The websocket is `ws://127.0.0.1:8001/tts/ws`. Override it with `POCKET_TTS_WS_URL` or `TTS_WS_URL`.

## Agent

`stt`, `llm`, and `vad` come from whatever else you already use. Ten lines for this TTS:

```python
from livekit.agents import Agent, AgentSession
from livekit.plugins.pocket import TTS

session = AgentSession(
    tts=TTS(voice="alba"),  # ws://127.0.0.1:8001/tts/ws, 24 kHz PCM
    # stt=..., llm=..., vad=...,
)
await session.start(
    agent=Agent(instructions="You are a helpful voice assistant."),
    room=ctx.room,
)
```

`TTS()` reads:

| Argument | Environment | Default |
| --- | --- | --- |
| `ws_url` | `POCKET_TTS_WS_URL`, then `TTS_WS_URL` | `ws://127.0.0.1:8001/tts/ws` |
| `voice` | `POCKET_TTS_VOICE` | `alba` |
| `sample_rate` | `POCKET_TTS_SAMPLE_RATE` | `24000` |
| `language` | `POCKET_TTS_LANGUAGE` | unset (Pocket is English) |
| `normalize` | `POCKET_TTS_NORMALIZE` | `true` |
| `pauses` | `POCKET_TTS_PAUSES` | `false` |
| `timeout` | `POCKET_TTS_TIMEOUT` | `30` seconds after text is sent |
| `api_key` | `POCKET_TTS_API_KEY` | unset (no `Authorization` header) |

If the server was started with `TTS_API_KEY`, pass the same secret as `api_key` or export `POCKET_TTS_API_KEY` on the LiveKit machine. The plugin sends `Authorization: Bearer <key>` on the websocket handshake. With neither set, it connects with no auth header, which is what a server without `TTS_API_KEY` expects.

`voice` is one string on the websocket config, not wav bytes on each text frame. The server resolves it. It can be a preset (`alba`), a wav path or `hf://` wav that the server can read, or a `.safetensors` voice state the server already exported. The first use of a wav pays the clone cost. Later turns, and the next server start, reuse the cached tensor.

`pauses=True` splices silence on the server. pocket-tts 2.1.0 does not do this itself. The LLM must emit the marker literally between sentences: `Hello. [pause:0.5] World.` Also `[pause]` (0.5s), `[pause:1.5]`, and `<break time="1.5s"/>`. Durations clamp at 10 seconds. Other SSML tags are rejected. Leave `pauses` off when the model is not emitting those markers.

One `TTS()` keeps one websocket for the agent session and reuses it for later spoken replies. `stream()` and `synthesize()` do not open a socket per reply or per PCM frame. A lock holds the socket for a single reply, so two replies cannot mix frames. LLM tokens are buffered with LiveKit's blingfire sentence tokenizer, sent as `text` frames, then one `flush`. The server sends int16 PCM as Pocket decodes each chunk, and the plugin pushes every frame into LiveKit as it arrives. A barge-in sends `cancel` on that same socket and leaves it open. The next reply sends `config` again, which clears the server's cancel flag, and any PCM still buffered from the interrupted reply is discarded. If the socket has closed, the next reply opens a new one. `api_key` is unchanged: every handshake, including a reconnect, carries `Authorization: Bearer <key>` when `api_key` or `POCKET_TTS_API_KEY` is set, and no auth header when it is not.

`synthesize("Hello.")` sends that string in one shot.

## Tests

```bash
python -m unittest tests.test_protocol
```

The protocol tests mock the socket. They do not load a model. The `TTS` import test skips when `livekit-agents` is not installed.

## Contributing this plugin

LiveKit documents one path for a plugin they will maintain: open a pull request against [livekit/agents](https://github.com/livekit/agents/blob/main/CONTRIBUTING.md) (and the [Node.js SDK](https://github.com/livekit/agents-js) if you also cover JavaScript). That path means their namespace (`livekit.plugins.<name>`), their CI, a CLA, and a docs page. See [Models → Contributing](https://docs.livekit.io/agents/models/#contributing).

This package should stay a separate repository. It depends on a server and protocol you run yourself, not on a hosted API LiveKit can key. Upstream it later only if you want LiveKit to own the client and you are ready to match their review bar.

An in-repo plugin looks like [livekit-plugins-neuphonic](https://github.com/livekit/agents/tree/main/livekit-plugins/livekit-plugins-neuphonic): `pyproject.toml`, `livekit/plugins/<name>/__init__.py`, `tts.py`, `version.py`, `log.py`, subclassing `livekit.agents.tts.TTS`.

## Not in this package

- Kokoro, OmniVoice, Piper, and the other engines in livekit-streaming-tts. Point this client at a server that already loaded Pocket.
- LiveKit Inference / LiveKit Cloud TTS. Audio comes from your websocket server.
- The server's sentence splitter. The client uses LiveKit's tokenizer and does not copy the server's abbreviation or comma-split rules.
