Address review: keep gpt-realtime family in the picker

Codex P1 follow-up to the audio-family fix: gpt-realtime /
gpt-realtime-mini / gpt-realtime-1.5 / gpt-realtime-2 are also
chat-completion-capable today per OpenAI's realtime guide. They
accept text in via /v1/chat/completions and /v1/responses, not
only the specialised /v1/realtime WebSocket transport. The
previous regex hid them from the picker AND the new regression
test pinned the regression -- both are fixed here.

Removed `realtime` from the mid-id feature suffix set, matching
the same reasoning that kept `audio` out. The audio-only endpoint
families (`tts-*`, `whisper-*`, `*-transcribe`) still get caught
above. Renamed the regression test to cover both families.
This commit is contained in:
Daniel Han 2026-05-22 12:34:38 +00:00
commit 6ea20c5aa7
2 changed files with 40 additions and 29 deletions

View file

@ -36,22 +36,21 @@ PROVIDER_REGISTRY: dict[str, dict[str, Any]] = {
#
# Pattern strategy:
# - Unambiguous *feature* indicators (tts/whisper/transcribe/
# realtime/image/embedding/moderation/sora) match anywhere
# in the id with `(?:^|-)` because OpenAI uses them as the
# primary qualifier on every variant -- e.g. canonical
# `tts-1` AND family variants `gpt-4o-tts`, `gpt-4o-mini-tts`.
# These words don't appear in legitimate chat model ids
# mid-string, so the mid-id match is intentional and safe.
# - `audio` is INTENTIONALLY NOT in the mid-id set: the
# `gpt-audio` / `gpt-audio-mini` / `gpt-audio-1.5` family is
# chat-completion-capable (text in / text or audio out via
# /v1/chat/completions and /v1/responses), so dropping them
# would hide supported chat models from the picker. The
# image/embedding/moderation/sora) match anywhere in the id
# with `(?:^|-)` because OpenAI uses them as the primary
# qualifier on every variant -- e.g. canonical `tts-1` AND
# family variants `gpt-4o-tts`, `gpt-4o-mini-tts`. These
# words don't appear in legitimate chat model ids mid-string,
# so the mid-id match is intentional and safe.
# - `audio` and `realtime` are INTENTIONALLY NOT in the
# mid-id set. Both the `gpt-audio*` and `gpt-realtime*`
# families are chat/responses-capable today -- per OpenAI's
# audio and realtime guides they accept text in via
# /v1/chat/completions and /v1/responses, not only the
# specialised /v1/realtime WebSocket transport. Dropping
# them hid supported chat models from the picker. The
# audio-only endpoint families (`tts-*`, `whisper-*`,
# `*-transcribe`) ARE caught above.
# - `realtime` keeps the mid-id match because the
# `gpt-realtime*` family is a separate /v1/realtime
# (WebSocket) endpoint, not chat-completions.
# `*-transcribe`) are still caught above.
# - `search` is INTENTIONALLY NOT in the mid-id set because
# `gpt-4o-search-preview` and `gpt-4o-mini-search-preview` are
# chat-with-retrieval models that absolutely belong in the
@ -69,9 +68,10 @@ PROVIDER_REGISTRY: dict[str, dict[str, Any]] = {
# Verified against the live /v1/models listing 2026-05-22.
"model_id_denylist": re.compile(
# Feature suffixes that mark a non-chat variant on any base.
# Note: `audio` is omitted on purpose -- see docstring above.
# Note: `audio` and `realtime` are omitted on purpose --
# see docstring above.
r"(?:^|-)(?:embedding|tts|whisper|moderation|image|"
r"realtime|transcribe|sora)\b"
r"transcribe|sora)\b"
# Legacy completion bases -- ^-anchored to avoid false
# positives on hypothetical future chat ids containing
# those words mid-string.

View file

@ -77,27 +77,39 @@ def test_openai_keeps_every_known_chat_family():
assert surviving == live, surviving
def test_openai_audio_family_is_chat_capable_and_kept():
def test_openai_audio_and_realtime_families_are_chat_capable_and_kept():
# Pin the chat/non-chat split that lives in the regex comment:
# gpt-audio is kept (chat completions accept it), gpt-realtime is
# dropped (separate /v1/realtime endpoint), gpt-4o-transcribe is
# dropped (audio-only input via /v1/audio/transcriptions).
# gpt-audio AND gpt-realtime are kept (both accept text in via
# /v1/chat/completions and /v1/responses, not only their
# specialised transports); gpt-4o-transcribe etc. are dropped
# (audio-only input via /v1/audio/transcriptions).
kept = _apply(
"openai",
[
"gpt-audio",
"gpt-audio-1.5",
"gpt-audio-mini",
"gpt-realtime",
"gpt-realtime-mini",
"gpt-realtime-1.5",
"gpt-realtime-2",
"gpt-4o-realtime-preview",
],
)
assert kept == ["gpt-audio", "gpt-audio-1.5", "gpt-audio-mini"], kept
assert kept == [
"gpt-audio",
"gpt-audio-1.5",
"gpt-audio-mini",
"gpt-realtime",
"gpt-realtime-mini",
"gpt-realtime-1.5",
"gpt-realtime-2",
"gpt-4o-realtime-preview",
], kept
dropped = _apply(
"openai",
[
"gpt-realtime",
"gpt-realtime-mini",
"gpt-4o-realtime-preview",
"gpt-4o-transcribe",
"gpt-4o-mini-transcribe",
],
@ -108,8 +120,9 @@ def test_openai_audio_family_is_chat_capable_and_kept():
def test_openai_drops_non_chat_ids():
noise = [
# Embeddings / TTS / image / moderation / whisper / etc.
# gpt-audio* is intentionally OMITTED from this list -- it is
# chat-capable. See test_openai_audio_family_is_chat_capable_and_kept.
# gpt-audio* and gpt-realtime* are intentionally OMITTED from
# this list -- both are chat-capable. See
# test_openai_audio_and_realtime_families_are_chat_capable_and_kept.
"text-embedding-3-small",
"text-embedding-3-large",
"text-embedding-ada-002",
@ -125,8 +138,6 @@ def test_openai_drops_non_chat_ids():
"gpt-image-2",
"gpt-image-1-mini",
"chatgpt-image-latest",
"gpt-realtime-2",
"gpt-4o-realtime-preview",
"gpt-4o-transcribe",
"gpt-4o-mini-transcribe",
"gpt-4o-mini-tts",