Skip to content

Feature request: opt-in upstream Responses WebSocket transport for OpenAI-compatible providers #2816

Closed

Description

@gulup

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

  1. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    streamingSSE, WebSocket, terminal stream frames
    on Aug 28, 2026
  2. github-actions commented on Aug 28, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  3. lidge-jun commented on Aug 28, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 48 / 80

    설명

    이 이슈는 OpenAI 호환 프로바이더(예: self-hosted sub2api의 /v1/responses)에도, 지금 ChatGPT Codex 백엔드에만 켜져 있는 Responses WebSocket 업스트림 전송을 프로바이더별 opt-in으로 열고 싶다는 요청이다. 지금 dev HEAD는 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.ts 523–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.ts shouldUseCodexWsUpstream) - URL이 캐논 ChatGPT responses HTTP URL과 완전 일치할 때만 WS를 켠다. 이 한 줄이 제3자 OpenAI-compat /responses를 전부 HTTP SSE로 남기는 직접 원인이다.
    경로 src/server/responses/fetch-helpers.ts providerFetch - WS 분기는 위 게이트에만 묶여 있다. 프로바이더 설정(예: 제안 upstreamWebsocket)을 여기까지 전달하지 않으면 플래그만 추가해도 동작하지 않는다.
    경로 src/types/config.ts websockets (523–524행) - 클라이언트 광고용 opt-in이다. 프로바이더 upstreamWebsocket과 이름이 비슷해서 문서/설정 UI에서 헷갈리기 쉽다. 서로 독립임을 문서에 적어야 한다.
    경로 src/types/provider.ts OcxProviderConfig - 지금 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 풀 오라우팅 - 이슈가 같이 적은 증상이다. upstreamWebsocket PR 범위에 넣지 말고, 재현되면 별 이슈/PR로 쪼개는 편이 안전하다.

    메인테이너의 판단이 필요한 지점

    • 프로바이더 opt-in upstreamWebsocket(기본 false) 방향을 받아들일지, 아니면 캐논 ChatGPT URL만 WS로 남길지.
    • WS 대상 URL을 “설정 baseUrl+responsesPath가 /responses로 끝나는 스트리밍 POST”로 할지, 더 좁은 allowlist(스킴/호스트)를 둘지.
    • create 프레임 바이트 한도를 캐논과 공유할지, 프로바이더 오버라이드를 둘지.
    • 클라이언트 websockets 오라우팅 증상을 이 이슈에 묶을지 분리할지.
    • 작성자 로컬 패치를 PR로 바로 받을지, 위 설계가 잠긴 뒤에만 받을지.

    너의 추천
    방향은 받아라. 캐논 URL 동작은 그대로, 기본 off인 프로바이더 플래그 + providerFetch/shouldUseCodexWsUpstream(또는 후속 헬퍼) 연결 + tests/ws-upstream.test.ts 행렬을 최소 범위로 한 PR을 열라고 답하라. 클라이언트 websockets 오라우팅은 이 PR에 넣지 말고 재현 로그가 있으면 후속 이슈로 분리하라. types/config 스플릿이나 지금 머지 트레인(#2802/#2815 축) 때문에 닫을 필요는 없다. help wanted로 두고 작성자 PR을 기다리면 된다.

    이 댓글은 grok-bot이 작성했습니다

  4. gulup commented on Aug 28, 2026

    @gulup
    ContributorAuthor

    Implemented in PR #2817.

    • Added provider-level upstreamWebsocket opt-in for OpenAI-compatible Responses endpoints.
    • Preserved the /responses path restriction, frame-size protection, and HTTP fallback behavior.
    • Fixed handling of the official Responses WebSocket response.done terminal event.
    • Added provider-routing and terminal-event regression tests.
    • Added regression coverage for DeepSeek exec_command → exec compatibility.

    The changes were tested locally. This issue will close automatically when the PR is merged.

  5. gulup commented on Aug 28, 2026

    @gulup
    ContributorAuthor

    Implemented in PR #2817, now targeting dev.

    • Added the provider-level upstreamWebsocket option and exposed it through the management API and Providers settings UI.
    • Restricted provider WebSocket routing to HTTPS → WSS. Plain HTTP endpoints continue to use HTTP SSE.
    • Normalized the upstream Responses WebSocket response.done event into the existing SSE terminal events.
    • Preserved the existing HTTP fallback and non-Responses HTTP routes.
    • Added focused regression coverage for routing, terminal-event normalization, management API persistence, and the UI toggle.

    The PR is ready for review. This issue will close automatically when the PR is merged.

  6. gulup commented on Aug 28, 2026

    @gulup
    ContributorAuthor

    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/PATCH API. 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.done into the existing SSE terminal events, preserves HTTP fallback behavior, and includes focused routing, relay, and management API coverage.

    PR: #2817

  7. gulup commented on Aug 31, 2026

    @gulup
    ContributorAuthor

    Update: PR #2817 has been rebased onto the latest dev branch and is ready for review.

    The implementation now:

    • fails closed when response.done has a missing, unknown, or non-terminal status;
    • documents the provider-specific Responses path and HTTPS-to-WSS behavior on all provider reference pages;
    • keeps plain HTTP on the existing SSE path and leaves non-Responses requests on HTTP;
    • passes the focused validation: 120 tests passed, 1 skipped, 0 failed, with TypeScript checking clean.

    The fork-triggered Cross-platform CI and React Doctor workflows 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.

  8. lidge-jun commented on Sep 1, 2026

    @lidge-jun
    Owner

    Landed on dev via #3216 (6fe46312c), carrying @gulup's #2817. Set "upstreamWebsocket": true on a provider (or through PATCH /api/providers) to dial an HTTPS /responses upstream over WebSocket; the default stays HTTP SSE, non-Responses and http:// endpoints are never upgraded, and any pre-open failure (including 426) falls back to the existing SSE path. Official WS response.done is mapped to the SSE terminal events with unknown status failing closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansenhancementNew feature or requestproviderProvider adapters, OpenAI-compat presets, upstream API quirksstreamingSSE, WebSocket, terminal stream frames

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions