Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d713fdd
Convert tool execution exception to runtime error, fixes #2204
aligokalppeker Dec 28, 2025
e3d08bf
Merge branch 'main' into bugfix/wrong_toolname_failure
aligokalppeker Dec 28, 2025
4c4bb24
Implement websocket connection configuration
aligokalppeker Dec 29, 2025
4dbe176
Eliminate sock factory, it is not required.
aligokalppeker Dec 29, 2025
717225d
Remove unused import
aligokalppeker Jan 5, 2026
ef0c19f
Fix unit tests.
aligokalppeker Jan 6, 2026
9ab3f93
Merge branch 'main' into feature/realtime_connection_features
aligokalppeker Jan 6, 2026
26d39e9
Remove unused imports.
aligokalppeker Jan 6, 2026
dbbcd4d
Merge branch 'main' into feature/realtime_connection_features
aligokalppeker Jan 6, 2026
af06277
Added unit tests for the new configurations.
aligokalppeker Jan 7, 2026
c4cb13e
Test suite for the TransportConfig
aligokalppeker Jan 8, 2026
8862c58
removed duplicated tests.
aligokalppeker Jan 12, 2026
031d38f
Merge branch 'main' into feature/realtime_connection_features
aligokalppeker Jan 13, 2026
ac3a92d
Merge branch 'main' into feature/realtime_connection_features
aligokalppeker Jan 19, 2026
6eb50df
Merge branch 'main' into feature/realtime_connection_features
aligokalppeker Jan 20, 2026
cd6787c
Merge branch 'main' into feature/realtime_connection_features
aligokalppeker Jan 20, 2026
d93796f
review comments
aligokalppeker Jan 20, 2026
5d46dd7
Transport config is moved.
aligokalppeker Jan 20, 2026
f7c27b4
lint fixes
aligokalppeker Jan 20, 2026
ae18f2b
unit test improvement
aligokalppeker Jan 20, 2026
c9b0db9
Merge branch 'main' into feature/realtime_connection_features
seratch Jan 20, 2026
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
62 changes: 55 additions & 7 deletions src/agents/realtime/openai_realtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
)
from openai.types.responses.response_prompt import ResponsePrompt
from pydantic import Field, TypeAdapter
from typing_extensions import TypeAlias, assert_never
from typing_extensions import NotRequired, TypeAlias, TypedDict, assert_never
from websockets.asyncio.client import ClientConnection

from agents.handoffs import Handoff
Expand Down Expand Up @@ -251,10 +251,25 @@ async def _build_model_settings_from_agent(
# during import on 3.9. We instead inline the union in annotations below.


class TransportConfig(TypedDict):
"""Low-level network transport configuration."""

ping_interval: NotRequired[float | None]
"""Time in seconds between keepalive pings sent by the client.
Default is usually 20.0. Set to None to disable."""

ping_timeout: NotRequired[float | None]
"""Time in seconds to wait for a pong response before disconnecting.
Set to None to disable ping timeout and keep an open connection (ignore network lag)."""

handshake_timeout: NotRequired[float]
"""Time in seconds to wait for the connection handshake to complete."""


class OpenAIRealtimeWebSocketModel(RealtimeModel):
"""A model that uses OpenAI's WebSocket API."""

def __init__(self) -> None:
def __init__(self, *, transport_config: TransportConfig | None = None) -> None:
self.model = "gpt-realtime" # Default model
self._websocket: ClientConnection | None = None
self._websocket_task: asyncio.Task[None] | None = None
Expand All @@ -267,6 +282,7 @@ def __init__(self) -> None:
self._created_session: OpenAISessionCreateRequest | None = None
self._server_event_type_adapter = get_server_event_type_adapter()
self._call_id: str | None = None
self._transport_config: TransportConfig | None = transport_config

async def connect(self, options: RealtimeModelConfig) -> None:
"""Establish a connection to the model and keep it alive."""
Expand Down Expand Up @@ -312,15 +328,47 @@ async def connect(self, options: RealtimeModelConfig) -> None:
raise UserError("API key is required but was not provided.")

headers.update({"Authorization": f"Bearer {api_key}"})
self._websocket = await websockets.connect(
url,
user_agent_header=_USER_AGENT,
additional_headers=headers,
max_size=None, # Allow any size of message

self._websocket = await self._create_websocket_connection(
url=url,
headers=headers,
transport_config=self._transport_config,
)
self._websocket_task = asyncio.create_task(self._listen_for_messages())
await self._update_session_config(model_settings)

async def _create_websocket_connection(
self,
url: str,
headers: dict[str, str],
transport_config: TransportConfig | None = None,
) -> ClientConnection:
"""Create a WebSocket connection with the given configuration.

Args:
url: The WebSocket URL to connect to.
headers: HTTP headers to include in the connection request.
transport_config: Optional low-level transport configuration.

Returns:
A connected WebSocket client connection.
"""
connect_kwargs: dict[str, Any] = {
"user_agent_header": _USER_AGENT,
"additional_headers": headers,
"max_size": None, # Allow any size of message
}

if transport_config:
if "ping_interval" in transport_config:
connect_kwargs["ping_interval"] = transport_config["ping_interval"]
if "ping_timeout" in transport_config:
connect_kwargs["ping_timeout"] = transport_config["ping_timeout"]
if "handshake_timeout" in transport_config:
connect_kwargs["open_timeout"] = transport_config["handshake_timeout"]

return await websockets.connect(url, **connect_kwargs)

async def _send_tracing_config(
self, tracing_config: RealtimeModelTracingConfig | Literal["auto"] | None
) -> None:
Expand Down
Loading