fix(gateway): support current Codex Realtime live routes

This commit is contained in:
ZheFox
2026-08-23 02:51:49 +08:00
parent ea4453321d
commit 2cd20da1ec
18 changed files with 3540 additions and 254 deletions
+65 -6
View File
@@ -1,13 +1,15 @@
# WebSocket transports
Aether exposes three independent WebSocket surfaces. They share transport
Aether exposes several independent WebSocket surfaces. They share transport
machinery, but not request schemas or continuation state:
| Public route | API format | Protocol |
| --- | --- | --- |
| `GET /v1/responses` | `openai:responses` | Responses WebSocket mode; every turn starts with `response.create`. |
| `GET /v1/realtime?model=...` | `openai:realtime` | OpenAI Realtime JSON events, including Base64 audio events. |
| `GET /v1/live[/{call_id}]` | `codex:live` | Codex Frameless/Live direct and WebRTC-sideband transport. |
| `GET /v1/realtime?model=...` with a first-party Codex `originator` | `codex:live` | Current Codex Realtime v2 direct WebSocket transport. |
| `POST /v1/realtime/calls`, `GET /v1/realtime?intent=quicksilver&call_id=...` | `codex:live` | Codex Realtime v1 AVAS WebRTC call creation and sideband transport. |
| `GET/POST /v1/live[/{call_id}]` | `codex:live` | Legacy Codex Frameless direct and WebRTC compatibility transport. |
Do not point one surface at an endpoint configured for another. In particular,
a Realtime or Live event is not passed through the Responses
@@ -62,13 +64,29 @@ Codex clients. It is related to the OpenAI Realtime API, but it is not the
Responses WebSocket protocol and never enters Aether's `response.create`
state machine:
- Direct WebSocket: `GET /v1/live?model=<global-model>`. The first client text
- Current WebRTC call creation: `POST /v1/realtime/calls?intent=quicksilver&architecture=avas`
with bounded `sdp` and `session` multipart parts. Aether normalizes those
selectors, applies the global-to-provider mapping, and rewrites the upstream
`Location` to `/v1/realtime/calls/<call-id>`.
- Current WebRTC sideband: `GET /v1/realtime?intent=quicksilver&call_id=<call-id>`.
The unique `intent=quicksilver` selector classifies it as Codex Live;
`call_id` alone remains on the separate OpenAI Realtime surface.
- Current direct WebSocket (Realtime v2):
`GET /v1/realtime?model=<authorized-global-model>`. V2 intentionally omits
`intent=quicksilver`; Aether uses the first-party Codex `originator` header
to distinguish it from ordinary OpenAI Realtime. The first client event is
still the opaque `session.update` frame generated by Codex; Aether forwards
it without converting it into a Responses `response.create` event.
- Realtime v1 direct WebSocket uses
`GET /v1/realtime?intent=quicksilver&model=<authorized-global-model>` and
sends `openai-alpha: quicksilver=v1` upstream. V2 does not send that header.
- Legacy direct WebSocket: `GET /v1/live?model=<global-model>`. The first client text
frame must be `session.update`; later text, binary, ping, pong, and close
frames are relayed opaquely.
- WebRTC call creation: `POST /v1/live` with bounded `sdp` and `session`
- Legacy WebRTC call creation: `POST /v1/live` with bounded `sdp` and `session`
multipart parts. Aether applies the existing global-to-provider model
mapping and rewrites the upstream `Location` to `/v1/live/<call-id>`.
- WebRTC sideband: `GET /v1/live/<call-id>`. Frameless sideband attaches to an
- Legacy WebRTC sideband: `GET /v1/live/<call-id>`. Frameless sideband attaches to an
already initialized call, so Aether neither waits for nor sends a second
`session.update` frame.
@@ -78,6 +96,45 @@ custom providers can add it in the endpoint editor. The
`responses_websocket.enabled` provider option belongs only to
`openai:responses` WebSocket mode and is not reused as the Live permission.
Codex Live also requires an authorized model mapping; adding the endpoint alone
is not enough. For WebRTC, Codex sends the selected model in the multipart
`session.model`. Aether treats that value as the downstream global model,
selects an existing mapping whose provider endpoint is `codex:live`, and
rewrites only `session.model` to the mapped upstream model. Configure Codex's
realtime model selection to an authorized global alias (the current Codex
default may be `gpt-realtime-1.5`, but that value is client-version dependent),
or create an authorized mapping for the alias the client already sends. Aether
does not invent a Live model name or add a bundled hard-coded model merely
because the endpoint is enabled. For Codex providers only, an existing model mapping scoped to
`openai:responses` (including the historical `/v1/responses` alias) can be
reused for Live. The provider endpoint and key must still explicitly allow
`codex:live`; OpenAI and custom providers do not receive this compatibility
rule.
For Codex Desktop/app-server, point both the call-creation and sideband
overrides at the same Aether origin when using a custom provider. Explicitly
setting both avoids a client-version-dependent fallback to the OpenAI origin:
```toml
model_provider = "aether"
experimental_realtime_webrtc_call_base_url = "https://<aether-host>/v1"
experimental_realtime_ws_base_url = "https://<aether-host>/v1"
experimental_realtime_ws_model = "<authorized-global-live-alias>"
[model_providers.aether]
name = "Aether"
base_url = "https://<aether-host>/v1"
wire_api = "responses"
```
The two experimental overrides are optional when the selected provider's
`base_url` already points at Aether, but if either is set they must resolve to
the same deployment so the authenticated call binding can be found. A missing
call-create request in Aether means the client did not select this provider or
failed before gateway routing; a call-create request without the matching
sideband usually means the sideband origin or credential differs. These
settings may change with newer Codex releases.
API-key and bearer providers can use direct WebSocket or WebRTC. ChatGPT OAuth
uses the official Codex backend for WebRTC call creation and the OpenAI Live
origin for its sideband; direct OAuth WebSocket and custom OAuth backend
@@ -109,7 +166,9 @@ created call that never attaches a sideband is not held against provider
concurrency after call creation.
For the public GA Realtime API's connection and session concepts, see the
[OpenAI Realtime guide](https://developers.openai.com/api/docs/guides/realtime).
[OpenAI Realtime guide](https://developers.openai.com/api/docs/guides/realtime),
[WebRTC connection guide](https://developers.openai.com/api/docs/guides/realtime-webrtc),
and [server-side controls guide](https://developers.openai.com/api/docs/guides/realtime-server-controls).
OpenAI's current WebSocket service supports named `stream_id` lanes: requests on
the same lane are FIFO, while different lanes may run concurrently. Aether's