diff --git a/examples/realtime_stt.py b/examples/realtime_stt.py index c46c187..35886d7 100644 --- a/examples/realtime_stt.py +++ b/examples/realtime_stt.py @@ -20,7 +20,7 @@ def on_mic(indata, frames, t, status): except queue.Full: pass - palabra = Palabra() # set your credentials here or vie ENV + palabra = Palabra() # set your credentials here or vie ENV async with palabra.stt(language="ru", translate_languages=["es", "en"]) as stt: diff --git a/examples/realtime_tts.py b/examples/realtime_tts.py index 48e609e..b2aed02 100644 --- a/examples/realtime_tts.py +++ b/examples/realtime_tts.py @@ -4,7 +4,7 @@ async def main(): - palabra = Palabra() # set your credentials here or vie ENV + palabra = Palabra() # set your credentials here or vie ENV async with palabra.tts(language="en", voice_id="default_low") as tts: # one-shot: send text, collect all chunks diff --git a/examples/sts_buffer_streaming.py b/examples/sts_buffer_streaming.py index 2cc8776..b20ebf3 100644 --- a/examples/sts_buffer_streaming.py +++ b/examples/sts_buffer_streaming.py @@ -25,7 +25,7 @@ async def feed_source(): async def main(): - palabra = Palabra() # set your credentials here or vie ENV + palabra = Palabra() # set your credentials here or vie ENV async with palabra.translation(source="en", targets=["es"]) as session: diff --git a/examples/sts_file_to_file.py b/examples/sts_file_to_file.py index d1c88ad..efdf147 100644 --- a/examples/sts_file_to_file.py +++ b/examples/sts_file_to_file.py @@ -1,6 +1,6 @@ from palabra_ai import Palabra -palabra = Palabra() # set your credentials here or vie ENV +palabra = Palabra() # set your credentials here or vie ENV palabra.translate_file( "speech_en.wav", diff --git a/examples/sts_mic_to_speakers.py b/examples/sts_mic_to_speakers.py index d6314dc..498dba7 100644 --- a/examples/sts_mic_to_speakers.py +++ b/examples/sts_mic_to_speakers.py @@ -35,7 +35,7 @@ def on_spk(outdata, frames, t, status): else: outdata.fill(0) - palabra = Palabra() # set your credentials here or vie ENV + palabra = Palabra() # set your credentials here or vie ENV async with palabra.translation(source="en", targets=["es"]) as session: async def feed(): diff --git a/examples/sts_multi_language.py b/examples/sts_multi_language.py index 8c972ce..27eae90 100644 --- a/examples/sts_multi_language.py +++ b/examples/sts_multi_language.py @@ -1,6 +1,6 @@ from palabra_ai import Palabra -palabra = Palabra() # set your credentials here or vie ENV +palabra = Palabra() # set your credentials here or vie ENV results = palabra.translate_file( "presentation.mp3", # mp3 needs: pip install palabra-ai[audio] diff --git a/pyproject.toml b/pyproject.toml index 811dc34..fff8d8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "palabra-ai" -version = "1.0.2" +version = "1.0.3" description = "Simple Python client for Palabra AI APIs" readme = "README.md" requires-python = ">=3.10" diff --git a/src/palabra_ai/client.py b/src/palabra_ai/client.py index 9ecd562..49ed850 100644 --- a/src/palabra_ai/client.py +++ b/src/palabra_ai/client.py @@ -27,6 +27,7 @@ GET_TASK_INTERVAL = 2.1 # server allows 1 get_task per 2s SESSION_RETRIES = 3 # create_session attempts on network errors / 5xx RETRY_BACKOFF = 0.5 # seconds; doubles per attempt (0.5, 1.0) +S2S_SESSION_INTENT = "api" @dataclass(frozen=True) @@ -69,12 +70,19 @@ def _headers(self) -> dict[str, str]: ) return {"ClientID": self.client_id, "ClientSecret": self.client_secret} - async def create_session(self) -> Session: + async def create_session(self, *, intent: str | None = None) -> Session: """Create a streaming session via REST. + intent is the session kind sent as data.intent; session-storage routes + and bills by it. Each product hardcodes its own value (s2s "api", + tts "tts_api", stt "stt") — not a user choice. Omitted when None. + Transient failures (network errors, 5xx) are retried up to SESSION_RETRIES times with backoff; 4xx fails immediately. """ + data: dict[str, Any] = {} + if intent is not None: + data["intent"] = intent last_error: Exception | None = None for attempt in range(SESSION_RETRIES): if attempt: @@ -84,9 +92,7 @@ async def create_session(self) -> Session: resp = await client.post( f"{self.api_url}/session-storage/session", headers=self._headers(), - json={ - "data": {} - }, + json={"data": data}, ) except httpx.TransportError as e: last_error = e @@ -332,7 +338,7 @@ def task(self) -> dict[str, Any]: async def __aenter__(self) -> TranslationSession: if self._session is None: - self._session = await self._palabra.create_session() + self._session = await self._palabra.create_session(intent=S2S_SESSION_INTENT) url = f"{self._session.ws_url}?token={self._session.publisher}" try: self._ws = await websockets.connect(url, ping_interval=10, ping_timeout=30, max_size=None) diff --git a/src/palabra_ai/stt.py b/src/palabra_ai/stt.py index 93f1f1c..a524448 100644 --- a/src/palabra_ai/stt.py +++ b/src/palabra_ai/stt.py @@ -18,6 +18,7 @@ from .client import Palabra, Session ASR_STREAM_PATH = "/asr/v1/speech-to-text/stream" +SESSION_INTENT = "stt" # session kind for billing routing DEFAULT_SAMPLE_RATE = 16000 # ASR recommended/default input rate (translation/TTS use 24k) _STT_TRANSCRIPT_TYPES = frozenset({"transcription", "translated_transcription"}) @@ -110,7 +111,7 @@ async def __aenter__(self) -> SttSession: base, token = self._direct else: if self._session is None: - self._session = await self._palabra.create_session() + self._session = await self._palabra.create_session(intent=SESSION_INTENT) base = _asr_ws_url(self._palabra.api_url) token = self._session.publisher url = f"{base}?{urlencode({'token': token, **self._params}, safe=',')}" diff --git a/src/palabra_ai/tts.py b/src/palabra_ai/tts.py index a457c50..dc1d797 100644 --- a/src/palabra_ai/tts.py +++ b/src/palabra_ai/tts.py @@ -20,6 +20,7 @@ # The Realtime TTS endpoint is fixed (not taken from the session response). TTS_STREAM_URL = "wss://stream.palabra.ai/tts-api/v1/text-to-speech/stream" +SESSION_INTENT = "tts_api" # session kind for billing routing @dataclass(frozen=True) @@ -80,7 +81,7 @@ async def __aenter__(self) -> TtsSession: url = f"{self._direct[0]}?token={self._direct[1]}" else: if self._session is None: - self._session = await self._palabra.create_session() + self._session = await self._palabra.create_session(intent=SESSION_INTENT) url = f"{TTS_STREAM_URL}?token={self._session.publisher}" try: self._ws = await websockets.connect(url, ping_interval=10, ping_timeout=30, max_size=None) diff --git a/uv.lock b/uv.lock index 84b39ff..cc7ee00 100644 --- a/uv.lock +++ b/uv.lock @@ -391,7 +391,7 @@ wheels = [ [[package]] name = "palabra-ai" -version = "1.0.2" +version = "1.0.3" source = { editable = "." } dependencies = [ { name = "httpx" },