nutritious-butcher-39524
03/02/2026, 12:43 PMtall-airline-16756
03/02/2026, 12:43 PMtall-airline-16756
03/02/2026, 12:44 PMnutritious-butcher-39524
03/02/2026, 1:39 PMtall-airline-16756
03/02/2026, 1:39 PMpyproject.toml, setup.py, or package.json)
• Any example usage snippet or demo script
If you prefer, paste just the README and I’ll start there and request any additional files I need.nutritious-butcher-39524
03/02/2026, 2:48 PMtall-airline-16756
03/02/2026, 2:48 PMpyproject.toml. This looks like a good start: package metadata, dependencies on livekit and livekit-agents, and clear project URLs. A few quick observations up front and then a short checklist of the next files I’ll need to do a full review.
What I checked so far
• Name, version, description, README and repo links are present.
• Python compatibility and dependency pins exist for pinch-sdk, livekit, and livekit-agents.
• Hatchling build config includes the livekit/ tree so your package will install into the livekit namespace — that matches the common pattern for LiveKit plugins.
Small issues and suggestions (actionable)
• requires-python: the file currently shows >=3.10 (HTML-escaped). Make sure it reads >=3.10 in the actual file.
• Consider tightening dependency version ranges if you want stricter compatibility guarantees (optional).
• Ensure you include any runtime extras or optional dependencies if parts of your plugin are optional (e.g., extra dependencies for streaming or testing), using optional-dependencies or extras in the project table.
Files I need to fully review your plugin and prepare a PR checklist Please paste the contents of these files (or the relevant excerpts) so I can review and give exact PR text and file-change recommendations:
• README.md (full content)
• The plugin implementation file(s) — the module that implements the Plugin class or registers the plugin (e.g., the file under livekit/plugins/…)
• package entry points / registration code (if any)
• Any example usage or demo script (shows how to register/use the plugin with an AgentSession)
• Tests (if present) or an example test script
• Any CI config (GitHub Actions) or linting configs (optional but helpful)
• If Python: confirm whether you want this included as an optional dependency in the main livekit/agents project and, if so, whether you added it to any dependency manifest there (I can recommend exact edits once I see plugin code).
What I’ll do once you paste those
• Review README for clarity, required auth/config steps, and example usage; suggest tweaks and a PR-ready README.
• Review plugin registration code and suggest the exact files and lines to add/update in the livekit/agents repo (e.g., dependency lists, index files).
• Provide a PR checklist and a ready-to-paste PR description template that includes testing instructions and reviewer guidance.
If you’d like, paste the README first and I’ll start there. If you prefer, paste multiple files at once — whatever is easiest.nutritious-butcher-39524
03/02/2026, 3:04 PMnutritious-butcher-39524
03/02/2026, 3:04 PMbash
pip install livekit-plugins-pinch
---
## Configuration
You need a Pinch API key. Get one at the [developers portal](https://portal.startpinch.com/dashboard/developers).
Set it in your environment:
bash
export PINCH_API_KEY=pk_your_key_here
That's the only credential this plugin needs. Your LiveKit credentials stay in your own app as usual.
---
## Usage
python
from livekit import rtc
from livekit.plugins.pinch import Translator, TranslatorOptions
async def entrypoint(ctx: JobContext):
await ctx.connect()
translator = Translator(
options=TranslatorOptions(
source_language="en-US",
target_language="es-ES",
voice_type="clone", # "clone" | "female" | "male"
)
)
@translator.on_transcript
def on_transcript(event):
if event.is_final:
print(f"[{event.type}] {event.text}")
await translator.start(ctx.room)
---
## Voice types
| Value | Description |
|-------|-------------|
| clone | Preserves the speaker's original voice identity (default) |
| female | Standard female voice |
| male | Standard male voice |
---
## Supported languages
Full list of language codes: [supported languages](https://www.startpinch.com/docs/supported-languages)
---
## License
Apache 2.0 — see [LICENSE](./LICENSE) for details.tall-airline-16756
03/02/2026, 3:05 PMrequires-python shows an HTML-escaped value (>=3.10). Make sure it reads >=3.10.
• Confirm tool.hatch.build.targets.wheel.packages = ["livekit"] is intentional so your package is installed into the livekit namespace. That is the expected pattern for LiveKit plugins, but double-check installation locally to ensure the livekit.plugins.pinch modules import correctly.
• Your package depends on livekit-agents>=0.8.0. Ensure the plugin API you use in translator.py is compatible with that minimum version (I’ll verify once I see translator.py).
B — README: suggested edits for clarity and reviewer friendliness
• Add a short “Prerequisites” section listing:
◦ LiveKit Agents version tested with (e.g., tested with livekit-agents X.Y.Z).
◦ Python versions you tested on (pyproject lists 3.10–3.12).
• Make installation explicit about the package source:
◦ If you plan to publish to PyPI, keep pip install livekit-plugins-pinch. If not yet published, note how to install from GitHub (e.g., `pip install git+https://github.com/pinch-eng/livekit-plugins-pinch.git`).
• Expand the Configuration section to include any environment variables, optional settings, and timeouts or region endpoints if applicable.
• Expand Usage to show:
◦ Minimal example for registering the plugin with an Agents AgentSession or JobContext (you have a JobContext example; add one showing how this integrates into a full AgentSession lifecycle if applicable).
◦ How to unsubscribe or stop the translator (cleanup/lifecycle).
• Add a “Testing” or “Local demo” section showing how maintainers can run a quick demo or unit tests. If testing requires Pinch credentials, explain how to run tests with mocks or set a CI secret.
• Add a short “Security & Privacy” section if relevant (e.g., whether audio or transcripts are sent to Pinch and how you recommend treating sensitive data).
• Add a one-line “Compatibility” note listing which LiveKit/Agents features your plugin integrates with (e.g., publishes an audio track, emits transcripts via callback).
C — Code & packaging checks to do before opening the PR
• translator.py — I still need this file to confirm:
◦ You implement the plugin registration pattern used by other LiveKit plugins (i.e., subclassing the Plugin base class or otherwise exposing a factory the agents project can import).
◦ The public API matches your README (methods like start(ctx.room), callbacks named @translator.on_transcript, etc.).
◦ Proper exception classes (PinchAuthError, PinchRateLimitError, etc.) are defined and documented.
◦ Lifecycle handling: that `start()`/`stop()` manage resources and cleanly close the Pinch session and any data channels.
◦ Streaming behavior: handling of audio chunks and backpressure to avoid overrun.
• Namespace packaging:
◦ Confirm the livekit/ tree layout in the wheel/sdist produces importable livekit.plugins.pinch modules.
◦ Ensure you do not include init.py in intermediate namespace packages if you want implicit namespace behavior — your comment indicates intentional implicit namespace packaging (PEP 420) which is fine.
• Tests:
◦ Provide at least one unit test or example script that demonstrates registration and a mocked transcript emission. If Pinch requires an external network call, use a mock in tests so reviewers can validate quickly.
• CI / formatting:
◦ If you have linters/formatters (black/ruff/isort) add a simple GitHub Actions job that runs tests and linting. This helps maintainers review and accept PRs faster.
D — PR checklist & PR description template (ready to paste)
Checklist (do these before opening PR)
• [ ] Confirm pyproject.toml has >=3.10 (not HTML-escaped).
• [ ] Include translator.py and any registration/entry code under livekit/plugins/pinch/.
• [ ] Add README items: Prereqs, usage example, testing instructions, compatibility.
• [ ] Add at least one unit test or demo script (with mocks for Pinch) and CI that runs it.
• [ ] Ensure package builds locally (python -m build or hatch build) and imports as from livekit.plugins.pinch import Translator.
• [ ] Verify exception classes and public API surface match README examples.
PR description template
Title: Add Pinch real-time speech-to-speech translation plugin (livekit/plugins/pinch)
Description: This PR adds a plugin that integrates Pinch real-time speech-to-speech translation with LiveKit Agents.
What it does:
• Translates incoming audio in real time (configurable source/target languages).
• Publishes translated audio back into the room as a separate track.
• Emits transcript events (original + translated) via a callback interface.
Files added:
• livekit/plugins/pinch/translator.py — core implementation and lifecycle handling
• livekit/plugins/pinch/models.py — dataclasses for TranslatorOptions and TranscriptEvent
• livekit/plugins/pinch/log.py — logger
• livekit/plugins/pinch/*init*.py — exports and version
• pyproject.toml, README.md, examples/, tests/
Testing & how to verify:
1. Install the package locally:
◦ pip install -e .
1. Run unit tests:
◦ pytest tests/
1. Run the example demo (uses mocked Pinch credentials):
◦ python examples/demo.py
◦ Or, set PINCH_API_KEY and run the demo against the real Pinch service.
Compatibility:
• Tested with Python 3.10–3.12
• Tested with livekit-agents >=0.8.0 (please confirm exact tested Agents version)
Notes for reviewers:
• The plugin uses implicit namespace packaging so it installs within the livekit namespace.
• Tests use mocks for external Pinch calls; real credentials are not required to run tests.
• If maintainers prefer a separate top-level package instead of namespace packaging, I can update packaging to publish as an independent package that still exposes livekit.plugins.pinch.
E — Next steps from me
• Please paste translator.py (the full file) and any example/demo script and tests you have. I’ll:
◦ Verify the plugin registration pattern and suggest the exact diff to add in the livekit/agents repo (e.g., where to list the optional dependency or index the plugin).
◦ Suggest any small API or doc tweaks that will make the PR land faster.
◦ Draft the exact changes you should make to dependency manifests or index files in livekit/agents if you want me to.
If you’d like, paste translator.py now and I’ll start the code-level review.nutritious-butcher-39524
03/02/2026, 3:12 PMapi_key "
"argument or set the PINCH_API_KEY environment variable."
)
self._api_key: str = resolved_key
self._options: TranslatorOptions = options
self._source_room: rtc.Room | None = None
self._pinch_room: rtc.Room | None = None
self._pinch_audio_source: rtc.AudioSource | None = None
self._source_audio_source: rtc.AudioSource | None = None
self._pinch_local_track: rtc.LocalAudioTrack | None = None
self._source_local_track: rtc.LocalAudioTrack | None = None
self._pinch_publication: rtc.LocalTrackPublication | None = None
self._source_publication: rtc.LocalTrackPublication | None = None
self._tasks: list[asyncio.Task] = []
self._transcript_callbacks: list[Callable[[TranscriptEvent], None]] = []
self._started = False
self._stopped = False
def on_transcript(
self, callback: Callable[[TranscriptEvent], None]
) -> Callable[[TranscriptEvent], None]:
"""Register a transcript callback. Works as a decorator or direct call.
Called for both ``original_transcript`` and ``translated_transcript`` events.
"""
self._transcript_callbacks.append(callback)
return callback
def remove_transcript_listener(self, callback: Callable[[TranscriptEvent], None]) -> None:
"""Remove a previously registered transcript callback."""
with contextlib.suppress(ValueError):
self._transcript_callbacks.remove(callback)
async def start(self, source_room: rtc.Room) -> None:
"""Start the Pinch translation pipeline.
Raises
------
PinchAuthError
If the API key is rejected.
PinchRateLimitError
If the API rate limit is exceeded.
PinchSessionError
If session creation or room connection fails after retries.
RuntimeError
If called after meth`stop`.
"""
if self._stopped:
raise RuntimeError(
"This Translator has already been stopped. "
"Create a new instance to start a fresh session."
)
if self._started:
logger.warning("Translator.start() called more than once; ignoring.")
return
self._started = True
self._source_room = source_room
logger.info(
"Pinch Translator starting src=%s tgt=%s voice=%s",
self._options.source_language,
self._options.target_language,
self._options.voice_type,
)
session = await self._create_session()
pinch_url: str = session["url"]
pinch_token: str = session["token"]
pinch_room_name: str = session["room_name"]
logger.info("Pinch session created room=%s", pinch_room_name)
self._pinch_room = await self._connect_with_retry(pinch_url, pinch_token)
logger.info("Connected to Pinch room room=%s", pinch_room_name)
self._pinch_audio_source = rtc.AudioSource(
sample_rate=_SAMPLE_RATE, num_channels=_NUM_CHANNELS
)
self._pinch_local_track = rtc.LocalAudioTrack.create_audio_track(
"pinch-input", self._pinch_audio_source
)
self._pinch_publication = await self._pinch_room.local_participant.publish_track(
self._pinch_local_track,
rtc.TrackPublishOptions(source=rtc.TrackSource.SOURCE_MICROPHONE),
)
self._source_audio_source = rtc.AudioSource(
sample_rate=_SAMPLE_RATE, num_channels=_NUM_CHANNELS
)
self._source_local_track = rtc.LocalAudioTrack.create_audio_track(
"pinch-translated", self._source_audio_source
)
self._source_publication = await self._source_room.local_participant.publish_track(
self._source_local_track,
rtc.TrackPublishOptions(source=rtc.TrackSource.SOURCE_MICROPHONE),
)
self._pinch_room.on("data_received", self._on_pinch_data_received)
self._tasks.append(asyncio.ensure_future(self._run_source_to_pinch_bridge()))
self._tasks.append(asyncio.ensure_future(self._run_pinch_to_source_bridge()))
logger.info("Pinch Translator started successfully.")
async def stop(self) -> None:
"""Shut down the translation pipeline. Safe to call multiple times."""
if self._stopped:
return
self._stopped = True
logger.info("Stopping Pinch Translator…")
if self._pinch_room is not None:
with contextlib.suppress(Exception):
self._pinch_room.off("data_received", self._on_pinch_data_received)
for task in self._tasks:
task.cancel()
if self._tasks:
await asyncio.gather(*self._tasks, return_exceptions=True)
self._tasks.clear()
if self._pinch_room is not None:
if self._pinch_publication is not None:
with contextlib.suppress(Exception):
await self._pinch_room.local_participant.unpublish_track(
self._pinch_publication.sid
)
with contextlib.suppress(Exception):
await self._pinch_room.disconnect()
self._pinch_room = None
if self._source_room is not None and self._source_publication is not None:
with contextlib.suppress(Exception):
await self._source_room.local_participant.unpublish_track(
self._source_publication.sid
)
self._pinch_audio_source = None
self._source_audio_source = None
logger.info("Pinch Translator stopped.")
async def _create_session(self) -> dict:
"""Create a Pinch translation session via the SDK."""
from pinch import PinchClient, SessionParams
from pinch.errors import (
PinchAuthError as _SdkAuthError,
PinchError as _SdkError,
PinchRateLimitError as _SdkRateLimitError,
)
params = SessionParams(
source_language=self._options.source_language,
target_language=self._options.target_language,
voice_type=self._options.voice_type,
)
try:
client = PinchClient(api_key=self._api_key)
session_info = await asyncio.to_thread(client.create_session, params)
except _SdkAuthError as exc:
raise PinchAuthError(str(exc)) from exc
except _SdkRateLimitError as exc:
raise PinchRateLimitError(str(exc)) from exc
except _SdkError as exc:
raise PinchSessionError(str(exc)) from exc
return {
"url": session_info.url,
"token": session_info.token,
"room_name": session_info.room_name,
}
async def _connect_with_retry(self, url: str, token: str) -> rtc.Room:
last_exc: Exception | None = None
for attempt in range(_MAX_CONNECT_RETRIES):
room = rtc.Room()
try:
await room.connect(url, token)
return room
except Exception as exc:
last_exc = exc
if attempt == _MAX_CONNECT_RETRIES - 1:
break
delay = _RETRY_BASE_DELAY * (2**attempt)
logger.warning(
"Connection attempt %d/%d failed: %s — retrying in %.1fs",
attempt + 1,
_MAX_CONNECT_RETRIES,
exc,
delay,
)
await asyncio.sleep(delay)
raise PinchSessionError(
f"Could not connect to the Pinch room after {_MAX_CONNECT_RETRIES} attempts."
) from last_excnutritious-butcher-39524
03/02/2026, 3:12 PMnutritious-butcher-39524
03/02/2026, 3:12 PMtall-airline-16756
03/02/2026, 3:13 PM>=3.10 with >=3.10.
2. Ensure the wheel/sdist includes the livekit/plugins/pinch files so importable as from livekit.plugins.pinch import Translator. Verify locally with:
◦ python -m build (or hatch build) and pip install the produced wheel in a clean venv, then import and run a quick smoke test.
1. In README and docs, state which LiveKit Agents version you tested against (e.g., livekit-agents >=0.8.0) and Python versions you validated.
Code review — translator.py (issues and recommendations)
• API/key handling
◦ Good: accepts api_key param, falls back to env var and raises a clear error if missing.
◦ Suggestion: document how to pass a test key vs production key and include a short note about rotating keys or using CI secrets.
• Session creation
◦ You correctly translate SDK exceptions to your own exception classes, which is good.
◦ Minor: the call to client.create_session is wrapped in asyncio.to_thread; keep as-is but add a short timeout guard or document expected latency so callers know session creation may block briefly.
• Connection retry
◦ Exponential backoff is implemented. Consider logging the exception type in the final error message for easier debugging.
◦ You raise PinchSessionError when all retries fail — good.
• Audio bridging (source → Pinch and Pinch → source)
◦ The AudioStream consumption loops are correct and use capture_frame to feed into audio sources.
◦ Potential improvement: handle backpressure explicitly when capture_frame might block (e.g., add small timeout or queue with maxsize to avoid unbounded memory when sink is slow).
◦ You register for existing participant tracks and on "track_subscribed" — good pattern. Make sure unsubscribing is robust in the presence of rapid connect/disconnect (you remove listeners in finally blocks, which is good).
• Data channel transcript handling
◦ You decode the data packet safely and guard against malformed JSON, then emit TranscriptEvent objects. Good.
◦ Suggestion: Validate the message fields more strictly (e.g., ensure text is a string) or coerce safely to avoid crashes in downstream callbacks.
• Callbacks
◦ You catch and log exceptions per callback — good. Consider documenting that callbacks run synchronously on the data thread and that users should avoid long-running sync work (or run their own tasks) to avoid blocking other callbacks.
• Lifecycle
◦ start/stop semantics are clear. There is a minor race: if start triggers tasks and stop is called quickly, tasks are cancelled and awaited — works, but add a short doc note about expected time to stop and best practices for cleanup.
• Logging
◦ You use a package-scoped logger; good. Consider adding a couple more debug logs for session creation payload and when tracks are published/unpublished (avoid logging secrets).
Packaging & tests
• Provide at least one unit test that:
◦ Mocks PinchClient.create_session to return a stubbed session (url/token/room_name).
◦ Mocks rtc.Room.connect and remote participant track publications so you can exercise start() and the streaming loops up to the point where you emit a TranscriptEvent via _on_pinch_data_received.
• Add a lightweight example script under examples/ that demonstrates:
◦ Creating Translator, registering a transcript callback, starting it with a mocked JobContext or a simple rtc.Room stub, then calling stop().
• Add GitHub Actions workflow to run tests (python-version: 3.10/3.11/3.12) and lint (black/ruff).
README: recommended edits (short, actionable)
• Add Prerequisites: tested LiveKit Agents version and Python versions.
• Installation note: if not yet on PyPI, include pip install from GitHub:
◦ pip install git+https://github.com/pinch-eng/livekit-plugins-pinch.git
• Usage: add a short example showing cleanup (await translator.stop()) and an example showing integration with AgentSession/JobContext lifecycle (how you obtain the room passed to start()).
• Testing: show how to run tests locally and mention that tests mock external network calls.
• Compatibility & security note: state that audio and transcripts are sent to the Pinch service and callers should treat them accordingly.
PR checklist (do these before opening PR)
• [ ] Fix requires-python to >=3.10.
• [ ] Build and test the wheel locally; confirm import path livekit.plugins.pinch.
• [ ] Add at least one unit test and an examples/demo script.
• [ ] Add CI workflow that runs tests and linting.
• [ ] Expand README with Prereqs, install-from-Git instructions (if not on PyPI), testing instructions, and lifecycle/cleanup note.
• [ ] Ensure translator.py docstrings mention which livekit-agents features are used and any expectations about runtime behavior (timeouts, stopping).
PR description template (ready to paste) Title: Add Pinch real-time speech-to-speech translation plugin (livekit/plugins/pinch)
Description: This PR adds a Pinch plugin that provides real-time speech-to-speech translation for LiveKit Agents.
Features:
• Creates a Pinch translation session and connects to the Pinch room.
• Bridges audio from source room to Pinch and publishes translated audio back to source room.
• Emits transcript events for original and translated text via a simple callback interface.
• Converts Pinch SDK errors to plugin-specific exceptions (PinchAuthError, PinchRateLimitError, PinchSessionError).
Files of interest:
• livekit/plugins/pinch/translator.py — core implementation
• livekit/plugins/pinch/models.py — TranslatorOptions, TranscriptEvent
• examples/ — demo usage
• tests/ — unit tests that mock the Pinch SDK and rtc interactions
• pyproject.toml, README.md
How to test:
1. Install locally:
◦ pip install -e .
1. Run tests:
◦ pytest
1. Run the example demo:
◦ python examples/demo.py
◦ (Set PINCH_API_KEY to exercise live calls; tests use mocks)
Notes for reviewers:
• The package uses implicit namespace packaging to install under the livekit namespace.
• Tests mock external calls to Pinch so maintainers can run CI without secrets.
• If maintainers prefer a different packaging strategy (separate top-level package), I can update the packaging.
Two quick, targeted questions so I can finish a final pass and craft the exact repo diffs you should open in livekit/agents
1. Do you want this listed as an optional dependency inside the livekit/agents repo (so consumers can install it as an extras group), or do you prefer to keep it published and referenced externally as a separate package? (Both are acceptable; I can prepare the exact dependency lines for either option.)
2. Do you already have tests or an examples/demo script you want me to review? If so paste them (or tell me where they are) and I’ll give a final pre-PR checklist and the exact file diffs to add in livekit/agents (where to update dependency lists or the plugin index).
If you want, I can now:
• Draft the exact patch/diff you should apply to livekit/agents to list this plugin as an optional dependency and where to add usage notes, or
• Review any tests/examples you paste and give final edits.
Which would you like next?nutritious-butcher-39524
03/02/2026, 7:17 PMtall-airline-16756
03/02/2026, 7:17 PM