Skip to content

Support OpenAI Realtime session control endpoints (JSON-only) #54

Description

@claw-io

Support OpenAI Realtime session control endpoints (JSON-only)

Split from #53 — covers the stateless JSON-only session control surface for OpenAI Realtime. The SIP/SDP calls endpoint and the WebSocket audio transport are tracked separately.

Background

Split out from #53 (Feature Request: Support embeddings and OpenAI Realtime API endpoints). Embeddings + streaming on /v1/chat/completions and /v1/responses are already supported; this issue covers only the Realtime JSON session control surface that is currently missing.

Searched src/ for realtime, client_secrets, realtime/sessions, realtime/transcription_sessions, realtime/translations — zero hits. The only WebSocket route registered in src/proxy_app/main.py:2213 is /v1/ws, which is a quota-stats push channel (quota_stats / error_event JSON envelopes), not an OpenAI Realtime transport.

Requested endpoints

Method Path Purpose
POST /v1/realtime/client_secrets Ephemeral Realtime session token
POST /v1/realtime/sessions Realtime session config (audio model)
POST /v1/realtime/transcription_sessions Transcription session config
POST /v1/realtime/translations Realtime translation session config

All four share the same shape: JSON request in, JSON response out, stateless passthrough.

Why these four are one issue

These endpoints share:

  • JSON body shape (no SDP, no binary, no streaming)
  • Stateless handler shape mirroring existing /v1/embeddings (main.py:1423-1488) and /v1/chat/completions (main.py:948-1104)
  • Per-exception error mapping (main.py:1490-1513) — same litellm exception → HTTP status mapping
  • Request-logger integrationlog_request_to_console(...) and RawIOLogger.log_request(...) both accept parsed JSON
  • Single small PR — each route is ~15 lines; bundling the four keeps the change reviewable

Splitting into one issue per endpoint would inflate review surface for no design benefit. They share enough code that shipping them together is the right granularity.

Why client_secrets is NOT separated out

client_secrets is the ephemeral-token issuer used by clients that don't want to embed a long-lived OpenAI API key in their browser/SDK. It's JSON-only and stateless — same shape as the other three. There is no design call here that doesn't also apply to sessions, transcription_sessions, and translations. Bundling is correct.

Proposed initial implementation

Scope: thin passthrough, no response transformation. Behavior per endpoint:

  1. Routing. Register each path in src/proxy_app/main.py alongside /v1/chat/completions (line 948), /v1/responses (line 1108), /v1/embeddings (line 1423). All four routes are simple @app.post(...) handlers that:

    • Apply the standard verify_api_key dependency (main.py:1429 style).
    • Forward the parsed JSON request body to the resolved provider via RotatingClient/litellm.
    • Return the upstream response body and status unchanged.
  2. Provider resolution. Reuse client.resolve_latest_async(...) (main.py:1001) and apply_model_alias(...) (main.py:998) so gpt-4o-realtime-preview follows the same alias rules as chat models. The example provider already maps gpt-4o-realtime-previewgpt-4o (src/rotator_library/providers/example_provider.py:326,344-345) — verify whether this should be removed for users who specifically target Realtime models.

  3. Error mapping. Reuse the existing per-exception mapping block (main.py:1490-1513 style) so auth/rate-limit/timeout errors get the same status codes clients already see on chat completions.

  4. Logging. Pipe through log_request_to_console(...) (main.py:1439) and RawIOLogger if ENABLE_RAW_LOGGING is set (main.py:1422 style), matching the existing pattern on /v1/embeddings.

  5. Quotas. Per-credential quota accounting applies — these endpoints are billable but low-cardinality.

  6. Tests. Add tests/ cases under the existing chat-completions / embeddings test style: happy-path round-trip for each of the four endpoints; 401 path with no PROXY_API_KEY; 400 path on malformed JSON; 429 path on rate limit (use a mock upstream).

Out of scope here

  • POST /v1/realtime/calls — separate issue (application/sdp body, raw body pass-through, different transport shape).
  • GET /v1/realtime WebSocket audio transport — separate issue.
  • Streaming passthrough on /v1/chat/completions and /v1/responses — already implemented (main.py:1031-1047, main.py:1152-1192).

References (HEAD on dev: d77a246)

  • src/proxy_app/main.py:1423-1488 — existing /v1/embeddings handler (closest analog for the new routes)
  • src/proxy_app/main.py:178-183EmbeddingRequest Pydantic model
  • src/proxy_app/main.py:948-1104chat_completions handler (alias resolution + raw logging pattern)
  • src/proxy_app/main.py:1108-1239responses_api handler (transcoder + streaming pattern)
  • src/proxy_app/main.py:1490-1513 — per-exception error mapping
  • src/rotator_library/providers/example_provider.py:326,344-345 — Realtime model alias map
  • src/rotator_library/providers/utilities/codex_ws_transport.py — existing outbound WebSocket plumbing (not directly reusable but reference for session/affinity patterns)

Checklist for review

  • Confirm scope is passthrough-only (no response transformation) before design diverges
  • Confirm whether gpt-4o-realtime-preview alias mapping in example_provider.py should remain or be Realtime-aware
  • Confirm per-credential quota accounting model for session control calls
  • Decide on response caching for client_secrets (likely no — ephemeral and short-lived)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions