Skip to content

fix(core): fall back on oversized websocket requests v2 - #43100

Open
neriousy wants to merge 1 commit into
v2from
v2-websocket-fallback
Open

fix(core): fall back on oversized websocket requests v2#43100
neriousy wants to merge 1 commit into
v2from
v2-websocket-fallback

Conversation

@neriousy

@neriousy neriousy commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • classify WebSocket close code 1009 before provider observation as rejected delivery
  • retry the current model request immediately through its HTTP fallback
  • keep the Session on HTTP after an oversized WebSocket rejection

Fixes #42572

Reproduction

V2 contains the Responses WebSocket transport, but normal OpenAI Sessions currently do not select it: the built-in OpenAI plugin registers session.http.request, and SessionModelRequest treats any HTTP hook as WebSocket-ineligible. To exercise the transport on either v2 or this branch, temporarily make this local-only change in packages/core/src/session/model-request.ts:

-      const webSocketEligible =
-        !(yield* hooks.has("session", "http.request")) && !(yield* hooks.has("session", "http.response"))
+      const webSocketEligible = true

Start the development server in one terminal:

OPENCODE_PASSWORD=issue-42572 \
OPENCODE_EXPERIMENTAL_OPENAI_RESPONSES_WEBSOCKET=true \
OPENCODE_CONFIG_PROJECT_DISABLE=true \
bun run --cwd packages/cli --conditions=browser src/index.ts \
  --log-level debug serve --hostname 127.0.0.1 --port 4097

In another terminal, generate six valid noisy PNGs and submit them through the raw API. Using the raw API avoids the CLI SSE reader's separate 16 MiB event limit.

ffmpeg -y -loglevel error \
  -f lavfi -i "nullsrc=s=1920x1080:r=1,noise=alls=100:allf=t+u" \
  -frames:v 6 /tmp/opencode-ws-%d.png

SESSION=$(jq -n --arg directory "$PWD" \
  '{model:{providerID:"openai",id:"gpt-5.6-sol"},location:{directory:$directory}}' |
  curl -fsSu opencode:issue-42572 \
    -H "content-type: application/json" \
    --data-binary @- http://127.0.0.1:4097/api/session |
  jq -r .data.id)

jq -n '{
  text:"Reply with only: received",
  files:[range(1;7) as $i | {
    uri:("file:///tmp/opencode-ws-" + ($i|tostring) + ".png"),
    name:("opencode-ws-" + ($i|tostring) + ".png")
  }]
}' |
curl -fsSu opencode:issue-42572 \
  -H "content-type: application/json" \
  --data-binary @- "http://127.0.0.1:4097/api/session/$SESSION/prompt"

while :; do
  RESULT=$(curl -fsSu opencode:issue-42572 \
    "http://127.0.0.1:4097/api/session/$SESSION/message?order=asc&limit=200" |
    jq -c '[.data[] | select(.type == "assistant")][-1] |
      select(.error != null or .time.completed != null) |
      {error, completed:.time.completed}' 2>/dev/null)
  [ -n "$RESULT" ] && printf '%s\n' "$RESULT" && break
  sleep 1
done

Observed against real OpenAI infrastructure with a 20,127,431-byte serialized response.create:

  • Unpatched v2: WebSocket closes with code 1009 and the assistant ends with provider.transport; there is no HTTP recovery.
  • This branch: the same request completes normally over HTTP, and a subsequent prompt in the same Session remains on HTTP without opening another WebSocket.

The warm-up test also exposed a separate V2 parser issue: ChatGPT may emit codex.rate_limits before response.created, which currently produces provider.invalid-output. That is independent of this fallback change.

Testing

  • bun test test/session-model-transport.test.ts test/session-model-transport-live.test.ts
  • bun typecheck --force
  • bunx prettier --check src/session/model-transport.ts test/session-model-transport.test.ts

@neriousy neriousy self-assigned this Aug 17, 2026
@neriousy neriousy changed the title fix(core): fall back on oversized websocket requests fix(core): fall back on oversized websocket requests v2 Aug 17, 2026
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

  • packages/core/src/session/model-transport.ts:438 — Stream.catch calls exchange.fallback() unconditionally once code is 1009/delivery rejected; if any caller can construct an exchange whose fallback is not implemented (or throws), a transport failure becomes a hard crash mid-stream — either guarantee fallback totality in the Exchange type or guard with an existence check that otherwise rethrows the original error.
  • packages/core/src/session/model-transport.ts:281 — Once owner.httpFallback flips, the session is pinned to HTTP forever, even though 1009 typically stems from one oversized prompt while ordinary traffic would fit comfortably back on the websocket; consider a heal probe (retry WS after a quiet period or N successful HTTP exchanges) or at least state the permanence explicitly in the warning log so operators know it is a one-way switch.
  • packages/core/src/session/model-transport.ts:430 — The warning records the close code but not the offending size; including the payload byte length (or the server-reported limit when available) would let users self-diagnose which artifact blew the frame limit instead of guessing.
  • packages/core/test/session-model-transport.test.ts:548 — Strong behavioral assertions (opened/fallbacks/closed counts); adding one assertion that the fallback metric/warning actually fired would pin the observability contract too, since a refactor silently dropping the logWarning/metric pair would still pass today.

— AI code review (automated)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants