Repository navigation
Feature request: opt-in upstream Responses WebSocket transport for OpenAI-compatible providers #2816
Description
Area
Streaming
What are you trying to accomplish?
I want opencodex to dial an OpenAI-compatible upstream over the Responses WebSocket transport, not only the canonical `chatgpt.com/backend-api/codex/responses` URL. Concretely: opencodex fronts a self-hosted aggregator (sub2api) whose `/v1/responses` path speaks the Responses WebSocket protocol, and whose upstream (OpenAI) leg is measurably faster over WS than over HTTP SSE.
What prevents this today?
`shouldUseCodexWsUpstream()` accepts only the canonical ChatGPT backend URL; every other provider always gets plain HTTP SSE. In addition, the aggregator classifies an HTTP-ingress request as `client_protocol_http` and forces its own upstream hop to HTTP SSE too (sub2api does this in `resolveOpenAIWSDecisionByClientTransport`), so the fast WS queue on the upstream leg is unreachable — the same ~1.0s vs ~3.9s TTFT gap opencodex itself measured on the ChatGPT backend.
Old sessions keep working, but new sessions opened after enabling the client-facing `websockets` option can be mis-routed to the default ChatGPT account pool and fail with `The '' model is not supported when using Codex with a ChatGPT account.` — which is exactly the inability to opt a specific provider into upstream WS.
What should OpenCodex do?
Add a provider-level opt-in flag, e.g. `upstreamWebsocket: true` on a provider entry. When set, streaming POST turns whose URL ends in `/responses` should be dialed over ws(s) using the existing bounded WS relay: send the body as a single `response.create` frame, re-encode event frames as SSE, and preserve routing, multi-key failover, usage sniffing, and the client stream shape. HTTP fallback on handshake rejection (401/403/429/5xx), upgrade timeout, oversized create frames, and Bun < 1.4 must remain in place. Canonical ChatGPT backend behaviour should not change.
Example usage or interface
```json
{
"providers": {
"igame": {
"adapter": "openai-responses",
"baseUrl": "https://sub2api.example.com",
"apiKey": "sk-...",
"upstreamWebsocket": true
}
}
}
```
With the flag enabled, the upstream hop becomes `wss://sub2api.example.com/v1/responses` and the aggregator records the request as its WebSocket (`ws_v2`) type instead of `stream`.
Alternatives or workarounds
Connecting Codex directly to the aggregator (skipping opencodex) does reach the WS path, but loses opencodex routing, multi-key pools, and the aggregated request pipeline. Keeping opencodex without the flag leaves every upstream hop on HTTP SSE.
Additional context
I have a working local patch implementing this: new provider field `upstreamWebsocket`, URL-derived wss dial (`wsUpstreamUrlFor`), `/responses` path eligibility check, and unit tests; `bun x tsc --noEmit` clean. Happy to turn it into a PR if this direction is acceptable. Related existing work: #1558 / #1487 (canonical ChatGPT upstream WS), #2426 / #2482 (WS frame sizing). I searched existing issues and found no request for third-party upstream WS.
Checks
- I searched existing issues and documentation.
- This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
- I removed secrets and personal data.
Activity
github-actions commentedon Aug 28, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
lidge-jun commentedon Aug 28, 2026
리뷰 · 우선순위 48 / 80
설명
이 이슈는 OpenAI 호환 프로바이더(예: self-hosted sub2api의
/v1/responses)에도, 지금 ChatGPT Codex 백엔드에만 켜져 있는 Responses WebSocket 업스트림 전송을 프로바이더별 opt-in으로 열고 싶다는 요청이다. 지금devHEAD는80e251a4d이고, 최근 방향은 커서 피커 umbrella(#2802), 쿼타 shortPercent 라우팅, Windows coordinator adoption fsync(#2815) 쪽이다. 이 요청은 그 머지 트레인과 직접 충돌하지 않는다. 대신 이미 들어간 캐논 WS 경로(#1558/#1487)를 제3자 baseUrl까지 넓히자는 설계 확장이다.현재 코드는 명확히 막혀 있다.
src/server/responses/ws-upstream.ts의shouldUseCodexWsUpstream은 Bun ≥ 1.4, POST, body가 문자열, 루트stream:true파싱까지 통과해도 URL이 정확히https://chatgpt.com/backend-api/codex/responses일 때만 true다.https://api.openai.com/v1/responses같은 다른 URL은 테스트에서도 false로 고정돼 있다.src/server/responses/fetch-helpers.ts의providerFetch는 그 게이트가 열릴 때만codexWsUpstreamFetch로 넘기고, 아니면 프로바이더 HTTP fetch(SSE)를 쓴다. 그래서 aggregator가 HTTP ingress를 보면 자기 업스트림도 HTTP SSE로 고정되고, 작성자가 말한 TTFT 이득(캐논 경로에서 측정한 ~1.0s vs ~3.9s와 같은 종류의 큐 차이)을 opencodex 경유로는 못 가져간다.제안 플래그
upstreamWebsocket: true는 의미가 분명하다. 기본 false, 켠 프로바이더만/responses(또는 설정된 responsesPath) 스트리밍 POST를 wss로 다이얼하고, 기존처럼response.create한 프레임 → 이벤트 프레임을 SSE로 재인코딩 → 라우팅·멀티키 페일오버·usage sniff·클라이언트 스트림 모양은 유지. 핸드셰이크 거절(401/403/429/5xx)·업그레이드 타임아웃·과대 create 프레임·Bun < 1.4 HTTP 폴백은 그대로. 캐논 ChatGPT URL 동작은 바꾸지 말라는 조건도 맞다. 작성자가 로컬 패치와 유닛 테스트까지 있다고 하니, 방향만 합의하면 PR로 받기 좋은 상태다.주의할 축이 두 개다. 첫째, 최상위 설정
websockets?: boolean(src/types/config.ts523–524행)은 Codex 클라이언트에supports_websockets를 광고하는 쪽이고, 업스트림 전송 선택과는 다른 스위치다. 이슈 본문의 “클라이언트 websockets를 켠 뒤 새 세션이 기본 ChatGPT 풀로 잘못 가서 모델 미지원” 증상은 이 플래그와 라우팅/계정 풀이 얽힌 별 버그일 수 있다. 그걸upstreamWebsocket만으로 고친다고 단정하면 안 된다. 둘째, #2426/#2482의 16MiB create 프레임 한도는 ChatGPT 백엔드 실측이다. 제3자 aggregator에도 같은 한도를 그대로 쓸지, 프로바이더별 한도/폴백을 둘지는 설계 선택이다. types.ts/config.ts 스플릿에 의해 무효화될 종류의 요청은 아니고, 중복 이슈도 검색상 없다. preview 배포 대상도 아니다.라인 107 (
src/server/responses/ws-upstream.tsshouldUseCodexWsUpstream) - URL이 캐논 ChatGPT responses HTTP URL과 완전 일치할 때만 WS를 켠다. 이 한 줄이 제3자 OpenAI-compat/responses를 전부 HTTP SSE로 남기는 직접 원인이다.경로
src/server/responses/fetch-helpers.tsproviderFetch- WS 분기는 위 게이트에만 묶여 있다. 프로바이더 설정(예: 제안upstreamWebsocket)을 여기까지 전달하지 않으면 플래그만 추가해도 동작하지 않는다.경로
src/types/config.tswebsockets(523–524행) - 클라이언트 광고용 opt-in이다. 프로바이더upstreamWebsocket과 이름이 비슷해서 문서/설정 UI에서 헷갈리기 쉽다. 서로 독립임을 문서에 적어야 한다.경로
src/types/provider.tsOcxProviderConfig- 지금upstreamWebsocket필드가 없다. 추가할 때 기본 false,openai-responses(및/responses를 실제로 치는 어댑터)에만 의미가 있음을 타입 주석으로 고정해야 한다.경로
tests/ws-upstream.test.ts-api.openai.com/v1/responses는 의도적으로 false다. 플래그 on/off, path eligibility, HTTP 폴백, 캐논 URL 불변을 이 파일에 행렬로 넣지 않으면 회귀가 바로 생긴다.경로 #2426/#2482 프레임 한도 - ChatGPT 실측 16MiB를 제3자에 그대로 적용할지 미정이다. 한도를 넘기면 SSE 폴백하는 현재 정책은 유지하되, 한도 값 자체를 프로바이더별로 둘지 메인테이너 판단이 필요하다.
경로 클라이언트
websockets+ ChatGPT 풀 오라우팅 - 이슈가 같이 적은 증상이다.upstreamWebsocketPR 범위에 넣지 말고, 재현되면 별 이슈/PR로 쪼개는 편이 안전하다.메인테이너의 판단이 필요한 지점
upstreamWebsocket(기본 false) 방향을 받아들일지, 아니면 캐논 ChatGPT URL만 WS로 남길지./responses로 끝나는 스트리밍 POST”로 할지, 더 좁은 allowlist(스킴/호스트)를 둘지.websockets오라우팅 증상을 이 이슈에 묶을지 분리할지.너의 추천
방향은 받아라. 캐논 URL 동작은 그대로, 기본 off인 프로바이더 플래그 +
providerFetch/shouldUseCodexWsUpstream(또는 후속 헬퍼) 연결 +tests/ws-upstream.test.ts행렬을 최소 범위로 한 PR을 열라고 답하라. 클라이언트websockets오라우팅은 이 PR에 넣지 말고 재현 로그가 있으면 후속 이슈로 분리하라. types/config 스플릿이나 지금 머지 트레인(#2802/#2815 축) 때문에 닫을 필요는 없다. help wanted로 두고 작성자 PR을 기다리면 된다.이 댓글은 grok-bot이 작성했습니다
gulup commentedon Aug 28, 2026
Implemented in PR #2817.
upstreamWebsocketopt-in for OpenAI-compatible Responses endpoints./responsespath restriction, frame-size protection, and HTTP fallback behavior.response.doneterminal event.exec_command→execcompatibility.The changes were tested locally. This issue will close automatically when the PR is merged.
gulup commentedon Aug 28, 2026
Implemented in PR #2817, now targeting
dev.upstreamWebsocketoption and exposed it through the management API and Providers settings UI.response.doneevent into the existing SSE terminal events.The PR is ready for review. This issue will close automatically when the PR is merged.
gulup commentedon Aug 28, 2026
Update: I narrowed the landing PR to the transport and server-side management changes.
The provider option is available through configuration and the provider management
POST/GET/PATCHAPI. This PR intentionally does not add a Providers UI toggle, since the feature is fully usable without it and keeping the scope focused avoids introducing an unrelated UI review requirement.The implementation keeps HTTPS-only provider WebSocket routing (HTTPS → WSS, plain HTTP → SSE), normalizes
response.doneinto the existing SSE terminal events, preserves HTTP fallback behavior, and includes focused routing, relay, and management API coverage.PR: #2817
gulup commentedon Aug 31, 2026
Update: PR #2817 has been rebased onto the latest
devbranch and is ready for review.The implementation now:
response.donehas a missing, unknown, or non-terminal status;The fork-triggered
Cross-platform CIandReact Doctorworkflows are currently waiting for source-repository admin approval. Once those checks run and the maintainer re-reviews the current head, this issue can be closed automatically when PR #2817 is merged.feat(responses): opt-in upstream Responses WebSocket transport (#2816,…
lidge-jun commentedon Sep 1, 2026
Landed on
devvia #3216 (6fe46312c), carrying @gulup's #2817. Set"upstreamWebsocket": trueon a provider (or throughPATCH /api/providers) to dial an HTTPS/responsesupstream over WebSocket; the default stays HTTP SSE, non-Responses andhttp://endpoints are never upgraded, and any pre-open failure (including 426) falls back to the existing SSE path. Official WSresponse.doneis mapped to the SSE terminal events with unknown status failing closed.feat(responses): opt-in upstream Responses WebSocket transport (lidge…
feat(responses): opt-in upstream Responses WebSocket transport (lidge…