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
4 changes: 2 additions & 2 deletions src/openai/resources/realtime/realtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None:
data = (
event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True)
if isinstance(event, BaseModel)
else json.dumps(event)
else json.dumps(maybe_transform(event, RealtimeClientEventParam))
)
self.__send_queue.enqueue(data)

Expand Down Expand Up @@ -1072,7 +1072,7 @@ def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None:
data = (
event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True)
if isinstance(event, BaseModel)
else json.dumps(event)
else json.dumps(maybe_transform(event, RealtimeClientEventParam))
)
self.__send_queue.enqueue(data)

Expand Down
131 changes: 131 additions & 0 deletions tests/test_realtime_manager_send_omit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""Regression tests for issue #3402: Pre-connect Realtime manager.send crashes on Omit in dict events.

These tests verify that the pre-connect send() methods on both sync and async
RealtimeConnectionManager properly transform Omit values before JSON serialization,
matching the behavior of the connected send() methods.
"""

from __future__ import annotations

import json
from unittest.mock import MagicMock

import pytest

from openai._types import omit
from openai.resources.realtime.realtime import (
AsyncRealtimeConnectionManager,
RealtimeConnectionManager,
)


def _make_sync_manager() -> RealtimeConnectionManager:
"""Create a minimal RealtimeConnectionManager for testing."""
return RealtimeConnectionManager(
client=MagicMock(),
call_id="test-call-id",
model="gpt-4o-realtime-preview",
extra_query={},
extra_headers={},
websocket_connection_options=MagicMock(),
)


def _make_async_manager() -> AsyncRealtimeConnectionManager:
"""Create a minimal AsyncRealtimeConnectionManager for testing."""
return AsyncRealtimeConnectionManager(
client=MagicMock(),
call_id="test-call-id",
model="gpt-4o-realtime-preview",
extra_query={},
extra_headers={},
websocket_connection_options=MagicMock(),
)


class TestRealtimeConnectionManagerSendOmit:
"""Test that RealtimeConnectionManager.send() handles Omit values."""

def test_send_dict_with_omit_strips_omit_values(self) -> None:
"""Pre-connect send() should strip Omit values from dict events before JSON serialization."""
manager = _make_sync_manager()

event = {"type": "response.cancel", "event_id": omit}

# This should NOT raise TypeError: Object of type Omit is not JSON serializable
manager.send(event)

# Verify the queued data has Omit stripped
items = manager._RealtimeConnectionManager__send_queue.drain()
assert len(items) == 1
data = json.loads(items[0])
assert data == {"type": "response.cancel"}
assert "event_id" not in data

def test_send_dict_without_omit_preserves_all_fields(self) -> None:
"""Pre-connect send() should preserve all fields when no Omit values present."""
manager = _make_sync_manager()

event = {"type": "response.cancel", "event_id": "evt_123"}
manager.send(event)

items = manager._RealtimeConnectionManager__send_queue.drain()
assert len(items) == 1
data = json.loads(items[0])
assert data == {"type": "response.cancel", "event_id": "evt_123"}

def test_send_dict_with_multiple_omit_fields(self) -> None:
"""Pre-connect send() should strip all Omit values from dict events."""
manager = _make_sync_manager()

event = {"type": "response.create", "event_id": omit, "response": {"modalities": omit}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fix nested omit test to use the typed field

This nested omit case still raises before the assertions because modalities is not a field in the current RealtimeResponseCreateParamsParam (the typed field is output_modalities), and _transform_typeddict preserves unknown keys instead of stripping their omit values. As written, maybe_transform leaves response.modalities = omit, so json.dumps(...) fails with TypeError and the new regression test suite fails; the async copy below has the same issue.

Useful? React with 👍 / 👎.

manager.send(event)

items = manager._RealtimeConnectionManager__send_queue.drain()
assert len(items) == 1
data = json.loads(items[0])
assert data == {"type": "response.create", "response": {}}


class TestAsyncRealtimeConnectionManagerSendOmit:
"""Test that AsyncRealtimeConnectionManager.send() handles Omit values."""

def test_send_dict_with_omit_strips_omit_values(self) -> None:
"""Pre-connect send() should strip Omit values from dict events before JSON serialization."""
manager = _make_async_manager()

event = {"type": "response.cancel", "event_id": omit}

# This should NOT raise TypeError: Object of type Omit is not JSON serializable
manager.send(event)

# Verify the queued data has Omit stripped
items = manager._AsyncRealtimeConnectionManager__send_queue.drain()
assert len(items) == 1
data = json.loads(items[0])
assert data == {"type": "response.cancel"}
assert "event_id" not in data

def test_send_dict_without_omit_preserves_all_fields(self) -> None:
"""Pre-connect send() should preserve all fields when no Omit values present."""
manager = _make_async_manager()

event = {"type": "response.cancel", "event_id": "evt_123"}
manager.send(event)

items = manager._AsyncRealtimeConnectionManager__send_queue.drain()
assert len(items) == 1
data = json.loads(items[0])
assert data == {"type": "response.cancel", "event_id": "evt_123"}

def test_send_dict_with_multiple_omit_fields(self) -> None:
"""Pre-connect send() should strip all Omit values from dict events."""
manager = _make_async_manager()

event = {"type": "response.create", "event_id": omit, "response": {"modalities": omit}}
manager.send(event)

items = manager._AsyncRealtimeConnectionManager__send_queue.drain()
assert len(items) == 1
data = json.loads(items[0])
assert data == {"type": "response.create", "response": {}}