Skip to content

Add Pinch translation plugin#4978

Open
MythriP wants to merge 4 commits intolivekit:mainfrom
pinch-eng:feat/add-livekit-plugins-pinch
Open

Add Pinch translation plugin#4978
MythriP wants to merge 4 commits intolivekit:mainfrom
pinch-eng:feat/add-livekit-plugins-pinch

Conversation

@MythriP
Copy link

@MythriP MythriP commented Mar 2, 2026

Adds support for Pinch via a LiveKit Agents plugin.

This plugin enables real-time speech translation using the Pinch API through the LiveKit Agents plugin interface.

Includes:

  • Translation plugin implementation
  • Agent-compatible integration
  • Basic usage example

The plugin registers via livekit.agents.plugins entry points and loads correctly within the LiveKit Agents workspace.

@CLAassistant
Copy link

CLAassistant commented Mar 2, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

logger.warning("Translator.start() called more than once; ignoring.")
return

self._started = True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 start() sets _started = True before fallible operations, permanently bricking the Translator on transient failure

If any awaited operation after line 144 raises (e.g. _create_session() returns an auth error, _connect_with_retry() exhausts retries, or publish_track() fails), the self._started flag is already True. Any subsequent call to start() will be silently ignored due to the guard at lines 140-142, making the Translator instance permanently unusable even though it never actually started.

Root Cause and Impact

self._started = True is set at line 144, before all the async operations that can fail (session creation at line 154, room connection at line 160, track publishing at lines 169/180). If any of these raises an exception, the flag remains True and start() can never be retried:

# Line 140-142: guard prevents retry
if self._started:
    logger.warning("Translator.start() called more than once; ignoring.")
    return

Additionally, if the failure occurs after self._pinch_room is connected (line 160) but before start() completes, the Pinch room connection is leaked — it's never disconnected because stop() won't know the partial state.

Impact: A single transient failure (network blip, rate limit, publish error) forces the user to discard the entire Translator and create a new one, with no indication that retrying start() is being silently swallowed.

Prompt for agents
In livekit-plugins/livekit-plugins-pinch/livekit/plugins/pinch/translator.py, the `start()` method (line 121) sets `self._started = True` at line 144 before any of the fallible async operations. This should be moved to the very end of `start()` (after line 189, before line 190) so that if any operation fails, `start()` can be retried. Additionally, wrap the body of `start()` in a try/except that performs cleanup (disconnect the pinch room if connected, unpublish tracks if published) and resets `self._started = False` before re-raising the exception. This ensures the Translator is not permanently bricked by a transient failure.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1 to +25
"""livekit-plugins-pinch — real-time speech-to-speech translation via Pinch."""

from .models import TranscriptEvent, TranslatorOptions
from .translator import (
PinchAuthError,
PinchError,
PinchRateLimitError,
PinchSessionError,
Translator,
)
from .version import __version__

__all__ = [
# Core classes
"Translator",
"TranslatorOptions",
"TranscriptEvent",
# Exceptions
"PinchError",
"PinchAuthError",
"PinchRateLimitError",
"PinchSessionError",
# Version
"__version__",
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Missing Plugin.register_plugin() call prevents framework integration

The Pinch plugin's __init__.py does not define a Plugin subclass or call Plugin.register_plugin(), unlike every other plugin in the repository (58 out of 59 existing plugins do this).

Impact on framework features

The Plugin.registered_plugins list is used in several critical framework paths:

  1. Logger configuration (livekit-agents/livekit/agents/cli/cli.py:1001-1004): Plugin loggers are auto-configured to match the agent's log level. Without registration, the livekit.plugins.pinch logger will remain at NOTSET.

  2. Forkserver preloading (livekit-agents/livekit/agents/worker.py:646): Registered plugin packages are preloaded in forkserver mode for faster subprocess startup. Pinch won't be preloaded.

  3. File downloading (livekit-agents/livekit/agents/cli/cli.py:1937-1940): The download-files CLI command iterates registered plugins. Pinch will be skipped.

  4. Hot reload watching (livekit-agents/livekit/agents/cli/watcher.py:40): The dev mode file watcher monitors registered plugin packages for changes. Pinch file changes won't trigger reloads.

Every other plugin follows this pattern (e.g. livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/__init__.py):

from livekit.agents import Plugin
from .log import logger

class DeepgramPlugin(Plugin):
    def __init__(self) -> None:
        super().__init__(__name__, __version__, __package__, logger)

Plugin.register_plugin(DeepgramPlugin())
Suggested change
"""livekit-plugins-pinch — real-time speech-to-speech translation via Pinch."""
from .models import TranscriptEvent, TranslatorOptions
from .translator import (
PinchAuthError,
PinchError,
PinchRateLimitError,
PinchSessionError,
Translator,
)
from .version import __version__
__all__ = [
# Core classes
"Translator",
"TranslatorOptions",
"TranscriptEvent",
# Exceptions
"PinchError",
"PinchAuthError",
"PinchRateLimitError",
"PinchSessionError",
# Version
"__version__",
]
"""livekit-plugins-pinch — real-time speech-to-speech translation via Pinch."""
from .models import TranscriptEvent, TranslatorOptions
from .translator import (
PinchAuthError,
PinchError,
PinchRateLimitError,
PinchSessionError,
Translator,
)
from .version import __version__
__all__ = [
# Core classes
"Translator",
"TranslatorOptions",
"TranscriptEvent",
# Exceptions
"PinchError",
"PinchAuthError",
"PinchRateLimitError",
"PinchSessionError",
# Version
"__version__",
]
from livekit.agents import Plugin
from .log import logger
class PinchPlugin(Plugin):
def __init__(self) -> None:
super().__init__(__name__, __version__, __package__, logger)
Plugin.register_plugin(PinchPlugin())
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants