From e078f1bd50758a35db7540572df249ba578c0797 Mon Sep 17 00:00:00 2001 From: egenthon-cmd Date: Thu, 16 Jul 2026 12:59:42 -0400 Subject: [PATCH 1/6] fix: pass region only on live session creation Region is only supported on POST /v2/live; stop sending it on pre-recorded and other live routes, and keep query strings at the end of URLs. Co-authored-by: Cursor --- packages/sdk-js/README.md | 2 +- packages/sdk-js/src/types.ts | 4 +- packages/sdk-js/src/v2/live/client.ts | 4 +- packages/sdk-js/src/v2/live/session.test.ts | 19 +++++++++ packages/sdk-js/src/v2/live/session.ts | 12 +++++- packages/sdk-js/src/v2/prerecorded/client.ts | 1 - packages/sdk-python/README.md | 2 +- .../src/gladiaio_sdk/client_options.py | 1 + .../src/gladiaio_sdk/network/helper.py | 36 ++++++++++++---- .../src/gladiaio_sdk/v2/live/_helpers.py | 8 ++++ .../src/gladiaio_sdk/v2/live/async_client.py | 11 +++-- .../src/gladiaio_sdk/v2/live/async_session.py | 7 +++- .../src/gladiaio_sdk/v2/live/client.py | 13 +++--- .../src/gladiaio_sdk/v2/live/session.py | 5 ++- .../v2/prerecorded/async_client.py | 6 +-- .../src/gladiaio_sdk/v2/prerecorded/client.py | 6 +-- .../sdk-python/tests/network/test_helper.py | 41 +++++++++++++++++++ .../tests/v2/live/test_connect_session.py | 27 +++++++++++- 18 files changed, 166 insertions(+), 39 deletions(-) create mode 100644 packages/sdk-python/tests/network/test_helper.py diff --git a/packages/sdk-js/README.md b/packages/sdk-js/README.md index 443a08b..2fbc6db 100644 --- a/packages/sdk-js/README.md +++ b/packages/sdk-js/README.md @@ -24,7 +24,7 @@ Import **`GladiaClient`** and create an instance. Provide an API key with **`apiKey`** or the **`GLADIA_API_KEY`** environment variable. [Get your API key here](https://docs.gladia.io/chapters/introduction/getting-started) in under a minute. -You can also set **`GLADIA_API_URL`** and **`GLADIA_REGION`** (`eu-west` / `us-west`). +You can also set **`GLADIA_API_URL`**. For live sessions, **`GLADIA_REGION`** (`eu-west` / `us-west`) is passed only on session creation (`POST /v2/live`). ### Node.js / Browser (ESM) diff --git a/packages/sdk-js/src/types.ts b/packages/sdk-js/src/types.ts index c7af1a4..a0353ca 100644 --- a/packages/sdk-js/src/types.ts +++ b/packages/sdk-js/src/types.ts @@ -115,9 +115,9 @@ export type GladiaClientOptions = { apiUrl?: string /** - * Region to use. + * Region for live session creation (POST /v2/live). Other routes do not support this param. * - * If not provided, the client will take the environment variable GLADIA_REGION and, if not provided either, it will default to 'eu-west'. + * If not provided, the client will take the environment variable GLADIA_REGION. */ region?: 'eu-west' | 'us-west' diff --git a/packages/sdk-js/src/v2/live/client.ts b/packages/sdk-js/src/v2/live/client.ts index 470a37e..1fa196a 100644 --- a/packages/sdk-js/src/v2/live/client.ts +++ b/packages/sdk-js/src/v2/live/client.ts @@ -12,15 +12,16 @@ export class LiveV2Client { private httpClient: HttpClient private webSocketClient: WebSocketClient private readonly liveTimeouts: InternalGladiaClientOptions['liveTimeouts'] + private readonly region: InternalGladiaClientOptions['region'] constructor(options: InternalGladiaClientOptions) { const httpBaseUrl = new URL(options.apiUrl) httpBaseUrl.protocol = httpBaseUrl.protocol.replace(/^ws/, 'http') this.liveTimeouts = options.liveTimeouts + this.region = options.region this.httpClient = new HttpClient({ baseUrl: httpBaseUrl, headers: options.httpHeaders, - ...(options.region ? { queryParams: { region: options.region } } : {}), retry: options.httpRetry, timeout: options.httpTimeout, }) @@ -37,6 +38,7 @@ export class LiveV2Client { startSession(options: LiveV2InitRequest): LiveV2Session { return new LiveV2Session({ options, + region: this.region, httpClient: this.httpClient, webSocketClient: this.webSocketClient, }) diff --git a/packages/sdk-js/src/v2/live/session.test.ts b/packages/sdk-js/src/v2/live/session.test.ts index f122ad3..18cfd9d 100644 --- a/packages/sdk-js/src/v2/live/session.test.ts +++ b/packages/sdk-js/src/v2/live/session.test.ts @@ -146,4 +146,23 @@ describe('LiveV2Session connectSession', () => { }) expect(session.sessionId).toBe('created-session-id') }) + + it('passes region only on POST /v2/live', async () => { + const session = new LiveV2Session({ + options: { sample_rate: 16000 }, + region: 'us-west', + httpClient, + webSocketClient, + }) + + await tick() + + expect(mockHttpPost).toHaveBeenCalledWith( + '/v2/live?region=us-west', + expect.objectContaining({ + body: expect.stringContaining('"sample_rate":16000'), + }) + ) + expect(session.sessionId).toBe('created-session-id') + }) }) diff --git a/packages/sdk-js/src/v2/live/session.ts b/packages/sdk-js/src/v2/live/session.ts index 9004fed..5db7dcb 100644 --- a/packages/sdk-js/src/v2/live/session.ts +++ b/packages/sdk-js/src/v2/live/session.ts @@ -36,18 +36,24 @@ export class LiveV2Session { private _status: LiveV2SessionStatus = 'starting' + private readonly region?: string + constructor({ options, + region, existingSession, httpClient, webSocketClient, }: { options: LiveV2InitRequest + /** Region query param for POST /v2/live only. Ignored when attaching to an existing session. */ + region?: string existingSession?: LiveV2InitResponse httpClient: HttpClient webSocketClient: WebSocketClient }) { this.sessionOptions = options + this.region = region this.httpClient = httpClient this.webSocketClient = webSocketClient this.abortController = new AbortController() @@ -132,7 +138,11 @@ export class LiveV2Session { private async initSession(): Promise { try { - return await this.httpClient.post(`/v2/live`, { + // region is only supported on session creation (POST /v2/live) + const initUrl = this.region + ? `/v2/live?region=${encodeURIComponent(this.region)}` + : '/v2/live' + return await this.httpClient.post(initUrl, { signal: this.abortController.signal, headers: { 'Content-Type': 'application/json', diff --git a/packages/sdk-js/src/v2/prerecorded/client.ts b/packages/sdk-js/src/v2/prerecorded/client.ts index 937c0f0..cf580a2 100644 --- a/packages/sdk-js/src/v2/prerecorded/client.ts +++ b/packages/sdk-js/src/v2/prerecorded/client.ts @@ -40,7 +40,6 @@ export class PreRecordedV2Client { this.httpClient = new HttpClient({ baseUrl: httpBaseUrl, headers: options.httpHeaders, - ...(options.region ? { queryParams: { region: options.region } } : {}), retry: options.httpRetry, timeout: options.httpTimeout, }) diff --git a/packages/sdk-python/README.md b/packages/sdk-python/README.md index 4e63b47..f8ae0bd 100644 --- a/packages/sdk-python/README.md +++ b/packages/sdk-python/README.md @@ -22,7 +22,7 @@ Import **`GladiaClient`** and create an instance. Provide an API key with **`api_key`** or the **`GLADIA_API_KEY`** environment variable. [Get your API key](https://docs.gladia.io/chapters/introduction/getting-started) in under a minute. -You can also set **`GLADIA_API_URL`** and **`GLADIA_REGION`** (`eu-west` / `us-west`). +You can also set **`GLADIA_API_URL`**. For live sessions, **`GLADIA_REGION`** (`eu-west` / `us-west`) is passed only on session creation (`POST /v2/live`). ### Sync client diff --git a/packages/sdk-python/src/gladiaio_sdk/client_options.py b/packages/sdk-python/src/gladiaio_sdk/client_options.py index 7f421c7..89b375d 100644 --- a/packages/sdk-python/src/gladiaio_sdk/client_options.py +++ b/packages/sdk-python/src/gladiaio_sdk/client_options.py @@ -103,6 +103,7 @@ class GladiaClientOptions: api_key: str | None = os.environ.get("GLADIA_API_KEY") api_url: str = os.environ.get("GLADIA_API_URL", "https://api.gladia.io") + # Only applied to live session creation (POST /v2/live). Other routes ignore it. region: Region | None = cast(Region | None, os.environ.get("GLADIA_REGION")) http_headers: dict[str, str] = field(default_factory=dict) http_retry: HttpRetryOptions = HttpRetryOptions() diff --git a/packages/sdk-python/src/gladiaio_sdk/network/helper.py b/packages/sdk-python/src/gladiaio_sdk/network/helper.py index dba199b..ed514f9 100644 --- a/packages/sdk-python/src/gladiaio_sdk/network/helper.py +++ b/packages/sdk-python/src/gladiaio_sdk/network/helper.py @@ -1,3 +1,6 @@ +from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit + + def matches_status(status: int, rules: list[int | tuple[int, int]] | None) -> bool: if not rules: return False @@ -13,12 +16,31 @@ def matches_status(status: int, rules: list[int | tuple[int, int]] | None) -> bo def build_url(base_url: str, url: str) -> str: - # If already absolute, return as is + """Join ``base_url`` and ``url``, keeping any query string at the end. + + Absolute ``url`` values are returned unchanged. Path segments are concatenated + (same as before) so proxy path prefixes are preserved, but base and relative + query params are merged onto the final URL instead of being left mid-path. + """ if url.startswith(("ws://", "wss://", "http://", "https://")): return url - base = base_url - if base.endswith("/") and url.startswith("/"): - return base + url[1:] - if base.endswith("/") or url.startswith("/"): - return base + url - return f"{base}/{url}" + + base = urlsplit(base_url) + rel = urlsplit(url) + + base_path = base.path + rel_path = rel.path + if base_path.endswith("/") and rel_path.startswith("/"): + path = base_path + rel_path[1:] + elif base_path.endswith("/") or rel_path.startswith("/"): + path = base_path + rel_path + elif base_path: + path = f"{base_path}/{rel_path}" + else: + path = rel_path + + params = dict(parse_qsl(base.query, keep_blank_values=True)) + params.update(dict(parse_qsl(rel.query, keep_blank_values=True))) + query = urlencode(params) + + return urlunsplit((base.scheme, base.netloc, path, query, rel.fragment or base.fragment)) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/_helpers.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/_helpers.py index 93e0b4c..2545eea 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/_helpers.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/_helpers.py @@ -5,6 +5,7 @@ import dataclasses from collections.abc import Callable from typing import Any, Literal, Protocol, TypeVar, overload +from urllib.parse import urlencode from gladiaio_sdk.v2.live.types import ( LiveV2ConnectedMessage, @@ -62,6 +63,13 @@ def with_acknowledgments_enabled(options: LiveV2InitRequest) -> LiveV2InitReques return dataclasses.replace(options, messages_config=msg_cfg) +def build_live_init_url(region: str | None = None) -> str: + """Build POST /v2/live URL. ``region`` is only supported on this route.""" + if region: + return f"/v2/live?{urlencode({'region': region})}" + return "/v2/live" + + def parse_ws_message(raw: Any) -> LiveV2WebSocketMessage: text = raw.decode("utf-8") if isinstance(raw, (bytes, bytearray)) else str(raw) return create_live_v2_web_socket_message_from_json(text) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_client.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_client.py index 7df0a0f..7e7bfe2 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_client.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_client.py @@ -20,14 +20,10 @@ def __init__(self, options: GladiaClientOptions) -> None: base_http_url = urlparse(options.api_url) base_http_url = base_http_url._replace(scheme=re.sub(r"^ws", "http", base_http_url.scheme)) - query_params: dict[str, str] = {} - if options.region: - query_params["region"] = options.region - self._http_client = AsyncHttpClient( base_url=base_http_url.geturl(), headers=options.http_headers, - query_params=query_params, + query_params={}, retry=options.http_retry, timeout=options.http_timeout, ) @@ -45,7 +41,10 @@ def __init__(self, options: GladiaClientOptions) -> None: def start_session(self, options: LiveV2InitRequest) -> LiveV2AsyncSession: return LiveV2AsyncSession( - options=options, http_client=self._http_client, ws_client=self._ws_client + options=options, + http_client=self._http_client, + ws_client=self._ws_client, + region=self._options.region, ) def connect_session(self, options: LiveV2ConnectSessionOptions) -> LiveV2AsyncSession: diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py index 19e0f57..94b7491 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py @@ -24,6 +24,7 @@ ) from ._helpers import ( LiveV2SessionEventsMixin, + build_live_init_url, emit_session_ending_events, emit_started_if_needed, maybe_emit_start_session_message, @@ -60,10 +61,12 @@ def __init__( http_client: AsyncHttpClient, ws_client: WebSocketClient, existing_session: LiveV2InitResponse | None = None, + region: str | None = None, ) -> None: self._options = options self._http_client = http_client self._ws_client = ws_client + self._region = region self._abort = asyncio.Event() self._event_emitter = AsyncIOEventEmitter() @@ -121,7 +124,9 @@ def end_session(self) -> None: async def _init_session(self) -> LiveV2InitResponse: try: options = with_acknowledgments_enabled(self._options) - resp = await self._http_client.post("/v2/live", json=options.to_dict()) + resp = await self._http_client.post( + build_live_init_url(self._region), json=options.to_dict() + ) return LiveV2InitResponse.from_json(resp.content) except Exception as err: _ = self._event_emitter.emit("error", err) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/client.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/client.py index 4e52204..253df96 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/client.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/client.py @@ -20,14 +20,10 @@ def __init__(self, options: GladiaClientOptions) -> None: base_http_url = urlparse(options.api_url) base_http_url = base_http_url._replace(scheme=re.sub(r"^ws", "http", base_http_url.scheme)) - query_params: dict[str, str] = {} - if options.region: - query_params["region"] = options.region - self._http_client = HttpClient( base_url=base_http_url.geturl(), headers=options.http_headers, - query_params=query_params, + query_params={}, retry=options.http_retry, timeout=options.http_timeout, ) @@ -44,7 +40,12 @@ def __init__(self, options: GladiaClientOptions) -> None: self._core = V2JobCore(base_path="/v2/live", kind="Live") def start_session(self, options: LiveV2InitRequest) -> LiveV2Session: - return LiveV2Session(options=options, http_client=self._http_client, ws_client=self._ws_client) + return LiveV2Session( + options=options, + http_client=self._http_client, + ws_client=self._ws_client, + region=self._options.region, + ) def connect_session(self, options: LiveV2ConnectSessionOptions) -> LiveV2Session: """Connect to an existing live session using its WebSocket URL and session ID. diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py index 04848d0..dbc5dbf 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py @@ -25,6 +25,7 @@ ) from ._helpers import ( LiveV2SessionEventsMixin, + build_live_init_url, emit_session_ending_events, emit_started_if_needed, maybe_emit_start_session_message, @@ -61,11 +62,13 @@ def __init__( http_client: HttpClient, ws_client: WebSocketClient, existing_session: LiveV2InitResponse | None = None, + region: str | None = None, ) -> None: self._options = options self._http_client = http_client self._ws_client = ws_client self._existing_session = existing_session + self._region = region self._event_emitter = EventEmitter() self._status: LiveV2SessionStatus = "starting" @@ -132,7 +135,7 @@ def end_session(self) -> None: def _init_session(self) -> LiveV2InitResponse: try: options = with_acknowledgments_enabled(self._options) - resp = self._http_client.post("/v2/live", json=options.to_dict()) + resp = self._http_client.post(build_live_init_url(self._region), json=options.to_dict()) return LiveV2InitResponse.from_json(resp.content) except Exception as err: _ = self._event_emitter.emit("error", err) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/async_client.py b/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/async_client.py index 6f4fe95..020414a 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/async_client.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/async_client.py @@ -37,14 +37,10 @@ def __init__(self, options: GladiaClientOptions) -> None: base_http_url = urlparse(options.api_url) base_http_url = base_http_url._replace(scheme=re.sub(r"^ws", "http", base_http_url.scheme)) - query_params: dict[str, str] = {} - if options.region: - query_params["region"] = options.region - self._http_client = AsyncHttpClient( base_url=base_http_url.geturl(), headers=options.http_headers, - query_params=query_params, + query_params={}, retry=options.http_retry, timeout=options.http_timeout, ) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/client.py b/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/client.py index 1026868..60934df 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/client.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/client.py @@ -37,14 +37,10 @@ def __init__(self, options: GladiaClientOptions) -> None: base_http_url = urlparse(options.api_url) base_http_url = base_http_url._replace(scheme=re.sub(r"^ws", "http", base_http_url.scheme)) - query_params: dict[str, str] = {} - if options.region: - query_params["region"] = options.region - self._http_client = HttpClient( base_url=base_http_url.geturl(), headers=options.http_headers, - query_params=query_params, + query_params={}, retry=options.http_retry, timeout=options.http_timeout, ) diff --git a/packages/sdk-python/tests/network/test_helper.py b/packages/sdk-python/tests/network/test_helper.py new file mode 100644 index 0000000..df10846 --- /dev/null +++ b/packages/sdk-python/tests/network/test_helper.py @@ -0,0 +1,41 @@ +"""Tests for network URL helpers.""" + +from __future__ import annotations + +from gladiaio_sdk.network.helper import build_url + + +def test_build_url_absolute_passthrough() -> None: + assert ( + build_url("wss://api.gladia.io", "wss://api.gladia.io/v2/live/ws?token=abc") + == "wss://api.gladia.io/v2/live/ws?token=abc" + ) + + +def test_build_url_joins_path_with_query_at_end() -> None: + assert ( + build_url("https://api.gladia.io", "/v2/live/ws?token=abc") + == "https://api.gladia.io/v2/live/ws?token=abc" + ) + + +def test_build_url_keeps_base_query_at_end() -> None: + # String concatenation would produce ...?region=eu-west/v2/live + assert ( + build_url("https://api.gladia.io?region=eu-west", "/v2/live") + == "https://api.gladia.io/v2/live?region=eu-west" + ) + + +def test_build_url_merges_query_params_relative_wins() -> None: + assert ( + build_url("https://api.gladia.io?region=eu-west", "/v2/live/ws?token=abc") + == "https://api.gladia.io/v2/live/ws?region=eu-west&token=abc" + ) + + +def test_build_url_preserves_proxy_path_prefix() -> None: + assert ( + build_url("wss://proxy.example/gladia", "/v2/live/ws?token=abc") + == "wss://proxy.example/gladia/v2/live/ws?token=abc" + ) diff --git a/packages/sdk-python/tests/v2/live/test_connect_session.py b/packages/sdk-python/tests/v2/live/test_connect_session.py index 52476a9..ef2c3b2 100644 --- a/packages/sdk-python/tests/v2/live/test_connect_session.py +++ b/packages/sdk-python/tests/v2/live/test_connect_session.py @@ -90,10 +90,11 @@ def create_async_session(self, url: str) -> FakeWebSocketSession: return self.create_session(url) -def _client_options() -> GladiaClientOptions: +def _client_options(**overrides: Any) -> GladiaClientOptions: return GladiaClientOptions( api_url="https://api.gladia.io", ws_retry=WebSocketRetryOptions(max_attempts_per_connection=0, max_connections=0), + **overrides, ) @@ -168,6 +169,30 @@ def test_start_session_still_calls_http_init(monkeypatch): assert session.join(timeout=2) +def test_start_session_passes_region_only_on_live_init(monkeypatch): + http_client = FakeHttpClient() + ws_client = FakeWebSocketClient() + + monkeypatch.setattr( + "gladiaio_sdk.v2.live.client.HttpClient", + lambda **kwargs: http_client, + ) + monkeypatch.setattr( + "gladiaio_sdk.v2.live.client.WebSocketClient", + lambda **kwargs: ws_client, + ) + + client = LiveV2Client(_client_options(region="us-west")) + session = client.start_session(LiveV2InitRequest(sample_rate=16000)) + + assert _wait_for(lambda: session.session_id == "created-session-id") + assert len(http_client.post_calls) == 1 + assert http_client.post_calls[0][0] == "/v2/live?region=us-west" + + session.end_session() + assert session.join(timeout=2) + + async def _run_async_connect_session_test( http_client: FakeAsyncHttpClient, ws_client: FakeWebSocketClient, From 4a6c9e442301aba3f1959a54dd4e29d0be599213 Mon Sep 17 00:00:00 2001 From: egenthon-cmd Date: Thu, 16 Jul 2026 13:07:11 -0400 Subject: [PATCH 2/6] style: format live async_session post call for ruff Co-authored-by: Cursor --- packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py index 94b7491..8fe8dc2 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py @@ -124,9 +124,7 @@ def end_session(self) -> None: async def _init_session(self) -> LiveV2InitResponse: try: options = with_acknowledgments_enabled(self._options) - resp = await self._http_client.post( - build_live_init_url(self._region), json=options.to_dict() - ) + resp = await self._http_client.post(build_live_init_url(self._region), json=options.to_dict()) return LiveV2InitResponse.from_json(resp.content) except Exception as err: _ = self._event_emitter.emit("error", err) From d6927756ad260cab24c842e0998ed94f78deffc7 Mon Sep 17 00:00:00 2001 From: egenthon-cmd Date: Mon, 20 Jul 2026 09:29:22 -0400 Subject: [PATCH 3/6] fix: params centralised for all (query params and region) --- packages/sdk-js/src/network/httpClient.ts | 6 +++--- packages/sdk-js/src/types.ts | 13 ++++++++++++- packages/sdk-js/src/v2/live/client.ts | 5 ++++- packages/sdk-js/src/v2/live/session.ts | 5 +++-- packages/sdk-js/src/v2/prerecorded/client.ts | 3 +++ .../sdk-python/src/gladiaio_sdk/client_options.py | 4 ++++ .../sdk-python/src/gladiaio_sdk/network/helper.py | 4 ++-- .../src/gladiaio_sdk/network/http_client.py | 6 +++--- .../sdk-python/src/gladiaio_sdk/v2/live/_helpers.py | 3 ++- .../src/gladiaio_sdk/v2/live/async_client.py | 6 ++++-- .../src/gladiaio_sdk/v2/live/async_session.py | 3 ++- .../sdk-python/src/gladiaio_sdk/v2/live/client.py | 6 ++++-- .../sdk-python/src/gladiaio_sdk/v2/live/session.py | 3 ++- .../src/gladiaio_sdk/v2/prerecorded/async_client.py | 6 ++++-- .../src/gladiaio_sdk/v2/prerecorded/client.py | 6 ++++-- 15 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/sdk-js/src/network/httpClient.ts b/packages/sdk-js/src/network/httpClient.ts index 62fd7d9..8d26d0d 100644 --- a/packages/sdk-js/src/network/httpClient.ts +++ b/packages/sdk-js/src/network/httpClient.ts @@ -1,5 +1,5 @@ import { deepMergeObjects, sleep } from '../helpers.js' -import type { HttpRetryOptions } from '../types.js' +import type { HttpRetryOptions, QueryParams } from '../types.js' import { initFetch } from './iso-fetch.js' import type { Headers } from './types.js' @@ -115,7 +115,7 @@ type RequestOptions = Omit & { export type HttpClientOptions = { baseUrl: string | URL headers?: Headers - queryParams?: Record + queryParams?: QueryParams retry: Required timeout: number } @@ -153,7 +153,7 @@ function isAbortError(error: unknown): boolean { export class HttpClient { private baseUrl: string | URL private defaultHeaders?: Headers - private defaultQueryParams?: Record + private defaultQueryParams?: QueryParams private retry: Required private timeout: number diff --git a/packages/sdk-js/src/types.ts b/packages/sdk-js/src/types.ts index a0353ca..42cddd9 100644 --- a/packages/sdk-js/src/types.ts +++ b/packages/sdk-js/src/types.ts @@ -90,6 +90,17 @@ export type LiveV2Timeouts = { getFile?: number } +/** + * Region for live session creation (POST /v2/live). + */ +export type Region = 'eu-west' | 'us-west' + +/** + * Default HTTP query parameters attached to every request from an HTTP client. + * Do not put `region` here — it is only supported on live session creation (POST /v2/live). + */ +export type QueryParams = Record + /** * Options for the Gladia Client. */ @@ -119,7 +130,7 @@ export type GladiaClientOptions = { * * If not provided, the client will take the environment variable GLADIA_REGION. */ - region?: 'eu-west' | 'us-west' + region?: Region /** * Custom headers to add to the HTTP requests. diff --git a/packages/sdk-js/src/v2/live/client.ts b/packages/sdk-js/src/v2/live/client.ts index 1fa196a..f3c752e 100644 --- a/packages/sdk-js/src/v2/live/client.ts +++ b/packages/sdk-js/src/v2/live/client.ts @@ -1,6 +1,7 @@ import { InternalGladiaClientOptions } from '../../internal_types.js' import { HttpClient } from '../../network/httpClient.js' import { WebSocketClient } from '../../network/wsClient.js' +import type { QueryParams, Region } from '../../types.js' import type { LiveV2InitRequest, LiveV2InitResponse, LiveV2Response } from './generated-types.js' import { LiveV2Session } from './session.js' import type { LiveV2ConnectSessionOptions } from './types.js' @@ -12,16 +13,18 @@ export class LiveV2Client { private httpClient: HttpClient private webSocketClient: WebSocketClient private readonly liveTimeouts: InternalGladiaClientOptions['liveTimeouts'] - private readonly region: InternalGladiaClientOptions['region'] + private readonly region?: Region constructor(options: InternalGladiaClientOptions) { const httpBaseUrl = new URL(options.apiUrl) httpBaseUrl.protocol = httpBaseUrl.protocol.replace(/^ws/, 'http') this.liveTimeouts = options.liveTimeouts this.region = options.region + const queryParams: QueryParams = {} this.httpClient = new HttpClient({ baseUrl: httpBaseUrl, headers: options.httpHeaders, + queryParams, retry: options.httpRetry, timeout: options.httpTimeout, }) diff --git a/packages/sdk-js/src/v2/live/session.ts b/packages/sdk-js/src/v2/live/session.ts index 5db7dcb..05968d7 100644 --- a/packages/sdk-js/src/v2/live/session.ts +++ b/packages/sdk-js/src/v2/live/session.ts @@ -2,6 +2,7 @@ import { EventEmitter } from 'eventemitter3' import { concatArrayBuffer, toUint8Array } from '../../helpers.js' import { HttpClient } from '../../network/httpClient.js' import { WebSocketClient, WebSocketSession, WS_STATES } from '../../network/wsClient.js' +import type { Region } from '../../types.js' import type { LiveV2InitRequest, LiveV2InitResponse, @@ -36,7 +37,7 @@ export class LiveV2Session { private _status: LiveV2SessionStatus = 'starting' - private readonly region?: string + private readonly region?: Region constructor({ options, @@ -47,7 +48,7 @@ export class LiveV2Session { }: { options: LiveV2InitRequest /** Region query param for POST /v2/live only. Ignored when attaching to an existing session. */ - region?: string + region?: Region existingSession?: LiveV2InitResponse httpClient: HttpClient webSocketClient: WebSocketClient diff --git a/packages/sdk-js/src/v2/prerecorded/client.ts b/packages/sdk-js/src/v2/prerecorded/client.ts index cf580a2..017153d 100644 --- a/packages/sdk-js/src/v2/prerecorded/client.ts +++ b/packages/sdk-js/src/v2/prerecorded/client.ts @@ -1,6 +1,7 @@ import { sleep } from '../../helpers.js' import type { InternalGladiaClientOptions } from '../../internal_types.js' import { HttpClient } from '../../network/httpClient.js' +import type { QueryParams } from '../../types.js' import type { PreRecordedV2AudioUploadResponse, PreRecordedV2InitTranscriptionRequest, @@ -37,9 +38,11 @@ export class PreRecordedV2Client { const httpBaseUrl = new URL(options.apiUrl) httpBaseUrl.protocol = httpBaseUrl.protocol.replace(/^ws/, 'http') this.prerecordedTimeouts = options.prerecordedTimeouts + const queryParams: QueryParams = {} this.httpClient = new HttpClient({ baseUrl: httpBaseUrl, headers: options.httpHeaders, + queryParams, retry: options.httpRetry, timeout: options.httpTimeout, }) diff --git a/packages/sdk-python/src/gladiaio_sdk/client_options.py b/packages/sdk-python/src/gladiaio_sdk/client_options.py index 89b375d..9d0f4c5 100644 --- a/packages/sdk-python/src/gladiaio_sdk/client_options.py +++ b/packages/sdk-python/src/gladiaio_sdk/client_options.py @@ -6,6 +6,10 @@ # Region parameter Region = Literal["eu-west", "us-west"] +# Default HTTP query parameters attached to every request from an HTTP client. +# Do not put ``region`` here — it is only supported on live session creation (POST /v2/live). +QueryParams = dict[str, str] + # Default timeouts (seconds) for general HTTP / WebSocket clients. DEFAULT_HTTP_TIMEOUT: float = 10 DEFAULT_WS_TIMEOUT: float = 10 diff --git a/packages/sdk-python/src/gladiaio_sdk/network/helper.py b/packages/sdk-python/src/gladiaio_sdk/network/helper.py index ed514f9..2a02dd0 100644 --- a/packages/sdk-python/src/gladiaio_sdk/network/helper.py +++ b/packages/sdk-python/src/gladiaio_sdk/network/helper.py @@ -39,8 +39,8 @@ def build_url(base_url: str, url: str) -> str: else: path = rel_path - params = dict(parse_qsl(base.query, keep_blank_values=True)) - params.update(dict(parse_qsl(rel.query, keep_blank_values=True))) + params = parse_qsl(base.query, keep_blank_values=True) + params.extend(parse_qsl(rel.query, keep_blank_values=True)) query = urlencode(params) return urlunsplit((base.scheme, base.netloc, path, query, rel.fragment or base.fragment)) diff --git a/packages/sdk-python/src/gladiaio_sdk/network/http_client.py b/packages/sdk-python/src/gladiaio_sdk/network/http_client.py index 27b04ad..04a8942 100644 --- a/packages/sdk-python/src/gladiaio_sdk/network/http_client.py +++ b/packages/sdk-python/src/gladiaio_sdk/network/http_client.py @@ -10,7 +10,7 @@ import httpx -from gladiaio_sdk.client_options import HttpRetryOptions +from gladiaio_sdk.client_options import HttpRetryOptions, QueryParams from gladiaio_sdk.network.helper import matches_status _schema_field_names_cache: dict[str, frozenset[str]] = {} @@ -313,7 +313,7 @@ def __init__( self, base_url: str, headers: dict[str, str], - query_params: dict[str, str], + query_params: QueryParams, retry: HttpRetryOptions, timeout: float, ) -> None: @@ -442,7 +442,7 @@ def __init__( self, base_url: str, headers: dict[str, str], - query_params: dict[str, str], + query_params: QueryParams, retry: HttpRetryOptions, timeout: float, ) -> None: diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/_helpers.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/_helpers.py index 2545eea..7f81266 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/_helpers.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/_helpers.py @@ -7,6 +7,7 @@ from typing import Any, Literal, Protocol, TypeVar, overload from urllib.parse import urlencode +from gladiaio_sdk.client_options import Region from gladiaio_sdk.v2.live.types import ( LiveV2ConnectedMessage, LiveV2ConnectingMessage, @@ -63,7 +64,7 @@ def with_acknowledgments_enabled(options: LiveV2InitRequest) -> LiveV2InitReques return dataclasses.replace(options, messages_config=msg_cfg) -def build_live_init_url(region: str | None = None) -> str: +def build_live_init_url(region: Region | None = None) -> str: """Build POST /v2/live URL. ``region`` is only supported on this route.""" if region: return f"/v2/live?{urlencode({'region': region})}" diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_client.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_client.py index 7e7bfe2..9931e88 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_client.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_client.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, final from urllib.parse import urlparse -from gladiaio_sdk.client_options import GladiaClientOptions +from gladiaio_sdk.client_options import GladiaClientOptions, QueryParams from gladiaio_sdk.network import AsyncHttpClient, WebSocketClient from gladiaio_sdk.v2.core import V2JobCore from gladiaio_sdk.v2.live.async_session import LiveV2AsyncSession @@ -20,10 +20,12 @@ def __init__(self, options: GladiaClientOptions) -> None: base_http_url = urlparse(options.api_url) base_http_url = base_http_url._replace(scheme=re.sub(r"^ws", "http", base_http_url.scheme)) + query_params: QueryParams = {} + self._http_client = AsyncHttpClient( base_url=base_http_url.geturl(), headers=options.http_headers, - query_params={}, + query_params=query_params, retry=options.http_retry, timeout=options.http_timeout, ) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py index 8fe8dc2..e9f281b 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py @@ -9,6 +9,7 @@ from pyee.asyncio import AsyncIOEventEmitter +from gladiaio_sdk.client_options import Region from gladiaio_sdk.v2.live.types import ( LiveV2ConnectedMessage, LiveV2ConnectingMessage, @@ -61,7 +62,7 @@ def __init__( http_client: AsyncHttpClient, ws_client: WebSocketClient, existing_session: LiveV2InitResponse | None = None, - region: str | None = None, + region: Region | None = None, ) -> None: self._options = options self._http_client = http_client diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/client.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/client.py index 253df96..0698499 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/client.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/client.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, final from urllib.parse import urlparse -from gladiaio_sdk.client_options import GladiaClientOptions +from gladiaio_sdk.client_options import GladiaClientOptions, QueryParams from gladiaio_sdk.network import HttpClient, WebSocketClient from gladiaio_sdk.v2.core import V2JobCore from gladiaio_sdk.v2.live.session import LiveV2Session @@ -20,10 +20,12 @@ def __init__(self, options: GladiaClientOptions) -> None: base_http_url = urlparse(options.api_url) base_http_url = base_http_url._replace(scheme=re.sub(r"^ws", "http", base_http_url.scheme)) + query_params: QueryParams = {} + self._http_client = HttpClient( base_url=base_http_url.geturl(), headers=options.http_headers, - query_params={}, + query_params=query_params, retry=options.http_retry, timeout=options.http_timeout, ) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py index dbc5dbf..a49ec48 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py @@ -10,6 +10,7 @@ from pyee import EventEmitter +from gladiaio_sdk.client_options import Region from gladiaio_sdk.v2.live.types import ( LiveV2ConnectedMessage, LiveV2ConnectingMessage, @@ -62,7 +63,7 @@ def __init__( http_client: HttpClient, ws_client: WebSocketClient, existing_session: LiveV2InitResponse | None = None, - region: str | None = None, + region: Region | None = None, ) -> None: self._options = options self._http_client = http_client diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/async_client.py b/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/async_client.py index 020414a..301d45a 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/async_client.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/async_client.py @@ -8,7 +8,7 @@ from typing import Any, BinaryIO, final from urllib.parse import urlparse -from gladiaio_sdk.client_options import GladiaClientOptions +from gladiaio_sdk.client_options import GladiaClientOptions, QueryParams from gladiaio_sdk.network import AsyncHttpClient from .core import ( @@ -37,10 +37,12 @@ def __init__(self, options: GladiaClientOptions) -> None: base_http_url = urlparse(options.api_url) base_http_url = base_http_url._replace(scheme=re.sub(r"^ws", "http", base_http_url.scheme)) + query_params: QueryParams = {} + self._http_client = AsyncHttpClient( base_url=base_http_url.geturl(), headers=options.http_headers, - query_params={}, + query_params=query_params, retry=options.http_retry, timeout=options.http_timeout, ) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/client.py b/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/client.py index 60934df..0221ef9 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/client.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/prerecorded/client.py @@ -8,7 +8,7 @@ from typing import Any, BinaryIO, final from urllib.parse import urlparse -from gladiaio_sdk.client_options import GladiaClientOptions +from gladiaio_sdk.client_options import GladiaClientOptions, QueryParams from gladiaio_sdk.network import HttpClient from .core import ( @@ -37,10 +37,12 @@ def __init__(self, options: GladiaClientOptions) -> None: base_http_url = urlparse(options.api_url) base_http_url = base_http_url._replace(scheme=re.sub(r"^ws", "http", base_http_url.scheme)) + query_params: QueryParams = {} + self._http_client = HttpClient( base_url=base_http_url.geturl(), headers=options.http_headers, - query_params={}, + query_params=query_params, retry=options.http_retry, timeout=options.http_timeout, ) From 21b85cf943d0542d35094d0f4f4ec17d84eae6bb Mon Sep 17 00:00:00 2001 From: egenthon-cmd Date: Mon, 20 Jul 2026 09:38:27 -0400 Subject: [PATCH 4/6] fix: matching region none in the types Co-authored-by: Cursor --- packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py | 2 +- packages/sdk-python/src/gladiaio_sdk/v2/live/session.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py index e9f281b..6620b8c 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/async_session.py @@ -67,7 +67,7 @@ def __init__( self._options = options self._http_client = http_client self._ws_client = ws_client - self._region = region + self._region: Region | None = region self._abort = asyncio.Event() self._event_emitter = AsyncIOEventEmitter() diff --git a/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py b/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py index a49ec48..5f69562 100644 --- a/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py +++ b/packages/sdk-python/src/gladiaio_sdk/v2/live/session.py @@ -69,7 +69,7 @@ def __init__( self._http_client = http_client self._ws_client = ws_client self._existing_session = existing_session - self._region = region + self._region: Region | None = region self._event_emitter = EventEmitter() self._status: LiveV2SessionStatus = "starting" From e0c098bad846e10c865d19ccbc7386679464e858 Mon Sep 17 00:00:00 2001 From: egenthon-cmd Date: Tue, 21 Jul 2026 11:05:58 -0400 Subject: [PATCH 5/6] fix: clarity query params def --- packages/sdk-js/src/v2/live/client.ts | 3 ++- packages/sdk-python/src/gladiaio_sdk/client_options.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk-js/src/v2/live/client.ts b/packages/sdk-js/src/v2/live/client.ts index f3c752e..0e2b2f2 100644 --- a/packages/sdk-js/src/v2/live/client.ts +++ b/packages/sdk-js/src/v2/live/client.ts @@ -24,7 +24,8 @@ export class LiveV2Client { this.httpClient = new HttpClient({ baseUrl: httpBaseUrl, headers: options.httpHeaders, - queryParams, + ...(options.region ? { queryParams: { region: options.region } } : {}), + retry: options.httpRetry, timeout: options.httpTimeout, }) diff --git a/packages/sdk-python/src/gladiaio_sdk/client_options.py b/packages/sdk-python/src/gladiaio_sdk/client_options.py index 9d0f4c5..12c8756 100644 --- a/packages/sdk-python/src/gladiaio_sdk/client_options.py +++ b/packages/sdk-python/src/gladiaio_sdk/client_options.py @@ -7,7 +7,6 @@ Region = Literal["eu-west", "us-west"] # Default HTTP query parameters attached to every request from an HTTP client. -# Do not put ``region`` here — it is only supported on live session creation (POST /v2/live). QueryParams = dict[str, str] # Default timeouts (seconds) for general HTTP / WebSocket clients. From adce444f648f4e8a498eaa8e9a2dfec7ba7d06b9 Mon Sep 17 00:00:00 2001 From: egenthon-cmd Date: Thu, 23 Jul 2026 15:38:39 -0400 Subject: [PATCH 6/6] fix: remove unused param --- packages/sdk-js/src/v2/live/client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sdk-js/src/v2/live/client.ts b/packages/sdk-js/src/v2/live/client.ts index 0e2b2f2..62a6697 100644 --- a/packages/sdk-js/src/v2/live/client.ts +++ b/packages/sdk-js/src/v2/live/client.ts @@ -1,7 +1,7 @@ import { InternalGladiaClientOptions } from '../../internal_types.js' import { HttpClient } from '../../network/httpClient.js' import { WebSocketClient } from '../../network/wsClient.js' -import type { QueryParams, Region } from '../../types.js' +import type { Region } from '../../types.js' import type { LiveV2InitRequest, LiveV2InitResponse, LiveV2Response } from './generated-types.js' import { LiveV2Session } from './session.js' import type { LiveV2ConnectSessionOptions } from './types.js' @@ -20,7 +20,6 @@ export class LiveV2Client { httpBaseUrl.protocol = httpBaseUrl.protocol.replace(/^ws/, 'http') this.liveTimeouts = options.liveTimeouts this.region = options.region - const queryParams: QueryParams = {} this.httpClient = new HttpClient({ baseUrl: httpBaseUrl, headers: options.httpHeaders,