Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions sdk/voicelive/azure-ai-voicelive/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Release History

## 1.3.1 (Unreleased)

### Other Changes

- The SDK now identifies Voice Live WebSocket connections through the `User-Agent` header and
`x-ms-client-sdk` query parameter.

## 1.3.0 (2026-08-03)

### Features Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
from azure.core.credentials import AzureKeyCredential
from azure.core.credentials_async import AsyncTokenCredential
from azure.core.exceptions import AzureError
from azure.core.pipeline.policies import UserAgentPolicy
from azure.ai.voicelive.models import ClientEvent, ServerEvent, RequestSession
from azure.ai.voicelive._version import VERSION

# === Local ===

Expand All @@ -67,6 +69,7 @@
]

log = logging.getLogger(__name__)
_USER_AGENT = UserAgentPolicy(sdk_moniker=f"ai-voicelive/{VERSION}").user_agent


def _json_default(o: Any) -> Any:
Expand Down Expand Up @@ -775,7 +778,7 @@ async def __aenter__(self) -> VoiceLiveConnection:
self.__connection_options.setdefault("heartbeat", 30)

auth_headers = await self._get_auth_headers()
headers = {**auth_headers, **dict(self.__extra_headers)}
headers = {"User-Agent": _USER_AGENT, **auth_headers, **dict(self.__extra_headers)}

session = aiohttp.ClientSession()
try:
Expand Down Expand Up @@ -821,7 +824,10 @@ def _prepare_url(self) -> str:
else ("ws" if parsed.scheme.startswith("http") else parsed.scheme)
)

params: dict[str, Any] = {"api-version": self.__api_version}
params: dict[str, Any] = {
"api-version": self.__api_version,
"x-ms-client-sdk": _USER_AGENT,
}
if self.__model is not None:
params["model"] = self.__model

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import pytest
from unittest.mock import AsyncMock, patch
from urllib.parse import parse_qs, urlparse

pytest.importorskip(
"aiohttp",
Expand All @@ -21,6 +22,7 @@
connect,
)
from azure.ai.voicelive.aio._patch import _VoiceLiveConnectionManager
from azure.ai.voicelive._version import VERSION
from azure.ai.voicelive.models import (
ClientEventSessionUpdate,
ClientEventResponseCreate,
Expand Down Expand Up @@ -208,6 +210,40 @@ async def test_response_resource_creation(self):
assert connection.response._connection is connection


@pytest.mark.asyncio
class TestConnectionIdentification:
"""Test SDK identification on WebSocket connections."""

async def _connect_and_get_headers(self, headers=None):
with patch("azure.ai.voicelive.aio._patch.aiohttp.ClientSession") as mock_client_session:
session = mock_client_session.return_value
session.ws_connect = AsyncMock(return_value=AsyncMock())
session.close = AsyncMock()

async with connect(
credential=AzureKeyCredential("test-key"),
endpoint="wss://test-endpoint.com",
model="gpt-realtime",
headers=headers,
):
pass

return session.ws_connect.await_args.kwargs["headers"]

async def test_connection_uses_sdk_user_agent(self):
"""Test the default User-Agent identifies the package and version."""
headers = await self._connect_and_get_headers()

assert "azsdk-python-ai-voicelive" in headers["User-Agent"]
assert VERSION in headers["User-Agent"]

async def test_connection_preserves_caller_user_agent(self):
"""Test a caller-supplied User-Agent overrides the SDK default."""
headers = await self._connect_and_get_headers({"User-Agent": "custom-user-agent"})

assert headers["User-Agent"] == "custom-user-agent"


class TestVoiceLiveConnectionIntegration:
"""Integration tests for VoiceLiveConnection."""

Expand Down Expand Up @@ -699,3 +735,33 @@ def test_url_uses_default_api_version(self):
url = manager._prepare_url()

assert "api-version=2026-07-15" in url

def test_url_includes_sdk_identifier(self):
"""Test that the connection URL identifies the SDK."""
manager = _VoiceLiveConnectionManager(
credential=self.credential,
endpoint=self.endpoint,
agent_config=None,
extra_query={},
extra_headers={},
)

query = parse_qs(urlparse(manager._prepare_url()).query)
sdk_identifier = query["x-ms-client-sdk"][0]

assert "azsdk-python-ai-voicelive" in sdk_identifier
assert VERSION in sdk_identifier

def test_url_preserves_traffic_type(self):
"""Test that SDK identification does not overwrite customer traffic tagging."""
manager = _VoiceLiveConnectionManager(
credential=self.credential,
endpoint=f"{self.endpoint}?trafficType=customer-tag",
agent_config=None,
extra_query={},
extra_headers={},
)

query = parse_qs(urlparse(manager._prepare_url()).query)

assert query["trafficType"] == ["customer-tag"]
Loading