Metadata-Version: 2.4
Name: livekit-gateway
Version: 0.1.2
Summary: Plivo audio-stream transport for livekit-agents — drop-in liblivekit_ffi.{so,dylib}
Author-email: Venky <bevenky11@gmail.com>
License: Apache-2.0
Project-URL: Repository, https://github.com/bevenky/livekit-gateway
Keywords: livekit,plivo,voice,audio-streaming,telephony,agent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Rust
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Topic :: Communications :: Telephony
Classifier: License :: OSI Approved :: Apache Software License
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# livekit-gateway

A LiveKit agents ↔ Plivo audio-stream bridge. Regular **[`livekit-agents`](https://github.com/livekit/agents)** runs
unchanged; calls land on Plivo numbers and route through this gateway 
via audio stream instead of a LiveKit SFU.

<div align="center">
  &nbsp;
  <img src="docs/livekit_gateway.svg" width="75%" alt="livekit-gateway architecture" />
  &nbsp;
</div>

## Install

```bash
pip install livekit-gateway
```

Wheel includes prebuilt artifacts for `macosx_arm64` (Apple Silicon)
and `manylinux2014_{x86_64,aarch64}`. Intel Macs aren't covered by
prebuilt wheels and need to build from source.

## Quick start

```python
# my_agent.py — stock livekit-agents, no modifications
import livekit_gateway     # MUST come before any livekit import

from livekit.agents import AgentServer, AgentSession, JobContext, JobProcess, cli
from livekit.plugins import deepgram, openai, silero


def prewarm(proc: JobProcess) -> None:
    # Loaded once per idle child, reused across all calls that child handles.
    # Without this, every call rebuilds Silero / Deepgram / OpenAI clients
    # on the hot path (~200-300 ms per call).
    proc.userdata["vad"] = silero.VAD.load()
    proc.userdata["stt"] = deepgram.STT(model="nova-3", language="en")
    proc.userdata["llm"] = openai.LLM(model="gpt-4o-mini")
    proc.userdata["tts"] = openai.TTS(model="tts-1", voice="alloy")


# `num_idle_processes` is the size of livekit-agents' warm child pool.
# Each idle child is a Python interpreter that has already run `prewarm`
# above; when a call lands, one is picked off the pool instantly (no
# fork + import + plugin-load on the hot path). Set this to your
# expected concurrent-call peak; on a small box, 2 is a fine start.
server = AgentServer(num_idle_processes=2)
server.setup_fnc = prewarm


@server.rtc_session()
async def entrypoint(ctx: JobContext):
    # `LIVEKIT_URL` is auto-set to the local gateway WS by `import
    # livekit_gateway` above (dev mode) or supplied by env (production —
    # `ws://<gateway-host>:7880`). You don't dial Plivo from here; Plivo
    # dials the gateway's answer URL and the gateway dispatches this
    # entrypoint with the call's metadata already populated in
    # `ctx.room`.
    session = AgentSession(
        stt=ctx.proc.userdata["stt"],
        llm=ctx.proc.userdata["llm"],
        tts=ctx.proc.userdata["tts"],
        vad=ctx.proc.userdata["vad"],
    )
    await ctx.connect()
    await session.start(agent=..., room=ctx.room)


if __name__ == "__main__":
    cli.run_app(server)
```

```bash
python my_agent.py dev
```

That's it. `import livekit_gateway` auto-spawns the gateway
subprocess if it isn't already running, sets `LIVEKIT_URL` to point
at it, and tears it down when Python exits. Plivo webhook + agent
worker registration + audio relay all happen behind the scenes.

> **Plivo side still needs one-time setup** — see [Plivo configuration](#plivo-configuration) below.

## Production

Run the gateway as a long-lived service and the agent in `start` mode (warm pool, not `dev`'s cold fork):

```bash
livekit-gateway                                                                   # terminal 1
LKG_NO_AUTO_GATEWAY=1 LIVEKIT_URL=ws://localhost:7880 python my_agent.py start    # terminal 2
```

Run the gateway under systemd (or any supervisor) so it survives agent restarts and existing calls don't drop. Multi-host scaling: run one gateway per host with the agent registering at it; if you point N agent workers at one gateway, the gateway round-robins jobs across the least-loaded ones.

## Configure

All non-secret gateway settings live in `config.toml` at the repo root (or wherever `$LKG_CONFIG_PATH` points). Every key is documented inline in that file — `[server]` for ports + public host, `[http]` for endpoint paths, `[stream]` for every Plivo `<Stream>` attribute, `[call_insights]` for the per-call observability summary. Secrets (`PLIVO_AUTH_ID`, `PLIVO_AUTH_TOKEN`) are env-only.

## Plivo configuration

Two URLs to set on your Plivo Application (Plivo Console → Voice → XML
Applications). Both point at the same public hostname; only the paths
differ, and both paths are configurable via `[http]` in `config.toml`.

- **Answer URL** → `https://<your-gateway>/answer` (`[http] answer_path`,
  default `/answer`). When a call lands, Plivo POSTs the call's form
  fields here (CallUUID, From, To, Direction, AccountSID) and the
  gateway replies with a `<Stream>` XML pointing back at the WSS audio
  endpoint with those fields appended as query params.
- **Hangup URL / Status Callback URL** → `https://<your-gateway>/status`
  (`[http] status_path`, default `/status`). Plivo POSTs call lifecycle
  events here (`hangup`, stream `start`/`stop`, etc.); the gateway uses
  these to close out the matching session cleanly.

## Releasing

PyPI wheels ship from CI; nothing is built locally for release. The flow mirrors agent-transport's:

1. Bump the version in:
   - `python/pyproject.toml` (`version` under `[project]`)
   - `python/livekit_gateway/__init__.py` (`__version__`)

   Use the [`bump-version`](./.claude/skills/bump-version/skill.md) skill — it asks for the target version, confirms, and edits both files in one pass.

2. Open a PR with **only** the version bump. Do not mix release bumps with feature changes.

3. Apply label `release-python-sdk` to the PR.

4. Merge to `main`. CI takes it from there:
   - **Build Python** (`.github/workflows/build-python.yml`) builds wheels for `macosx_arm64` and `manylinux2014_{x86_64,aarch64}`.
   - **Publish Python** (`.github/workflows/publish-python.yml`) downloads them, publishes to PyPI via the trusted publisher (OIDC, no token), and creates a `python-vX.Y.Z` GitHub Release with notes auto-built from PRs labelled `core` or `python` since the previous tag.

The PyPI description is sourced from `python/README.md`, which the **Build Python** workflow rewrites from the repo-root `README.md` before each cibuildwheel run. So edit only the root README; CI keeps them in sync. (`python/README.md` is kept in the tree because `pyproject.toml`'s `readme =` pointer resolves relative to that directory — without the file, local `pip install -e ./python` would fail.)

The Rust workspace version in `Cargo.toml` is decoupled — nothing publishes those crates user-facing, so bumping is cosmetic. Leave it unless you have a reason.

### Prerequisites (one-time)

- PyPI: trusted publisher for `livekit-gateway` pointing at `publish-python.yml` (no environment configured — the workflow doesn't claim one). See https://docs.pypi.org/trusted-publishers/.
- GitHub: label `release-python-sdk`.

### Release notes

The publish workflow filters PRs since the last `python-v*` tag down to those labelled `core` or `python`, then prepends them as the release body. Apply one of those labels on feature/fix PRs you want surfaced in the changelog. Release-bump PRs themselves should only carry `release-python-sdk` (no `python` / `core` — otherwise the bump PR title shows up in its own notes).

## License

MIT.
