Skip to content

Fix OpenAI SSE relaying for Droid custom models#315

Open
amanshresthaa wants to merge 1 commit intoautomazeio:mainfrom
amanshresthaa:codex/fix-droid-openai-sse
Open

Fix OpenAI SSE relaying for Droid custom models#315
amanshresthaa wants to merge 1 commit intoautomazeio:mainfrom
amanshresthaa:codex/fix-droid-openai-sse

Conversation

@amanshresthaa
Copy link
Copy Markdown

@amanshresthaa amanshresthaa commented Apr 2, 2026

Summary

  • normalize OpenAI SSE responses before forwarding them to local clients like Droid
  • decode chunked event streams and rewrite headers so the proxy emits a clean close-delimited stream
  • add regression coverage for fragmented event: / data: frames and Data-slice edge cases

Root Cause

Droid custom OpenAI models could fail through VibeProxy with OpenAI OAuth because VibeProxy forwarded backend OpenAI Responses streams too literally. In practice the client could receive partial SSE fragments such as event: response.created without the matching data: payload in the same message boundary, which caused Droid to fail with JSON Parse error: Unexpected EOF.

A second issue showed up while fixing this: some backend responses arrived with Transfer-Encoding: chunked, so the proxy needed to decode chunked bodies before reframing SSE. There were also Data index handling edge cases that could trap when working with sliced buffers.

Changelog

  • add ResponsesSSEFramer to buffer and merge fragmented OpenAI SSE frames
  • add HTTPChunkedDecoder to decode chunked event-stream bodies before reframing
  • add HTTPResponseRelay to rewrite SSE response headers and normalize response forwarding
  • route proxied responses in ThinkingProxy through the relay instead of forwarding raw chunks
  • keep /v1/chat/completions and /v1/responses streaming behavior consistent for Droid and similar clients
  • add regression tests for split event/data frames, chunked event streams, and sliced Data buffer handling

Implementation Details

New relay path

  • ThinkingProxy.receiveResponse now carries the request path into a response relay
  • streamNextChunk forwards backend chunks through HTTPResponseRelay
  • forwardRelayedResponseChunks and sendResponseChunks serialize rewritten chunks back to the client

SSE framing

  • hold incomplete event: frames until the matching data: line arrives
  • preserve complete SSE message boundaries with normalized trailing delimiters
  • avoid emitting standalone event-only messages that break OpenAI SDK parsers

Chunked decoding

  • detect Transfer-Encoding: chunked on event-stream responses
  • decode HTTP chunk bodies before SSE processing
  • rewrite response headers to remove chunked transfer encoding and return Connection: close

Safety / regression handling

  • fix Data index math so sliced buffers do not trap while removing ranges
  • keep non-event-stream responses on the passthrough path

Testing

  • swift build
  • manual curl verification for /v1/chat/completions streaming through VibeProxy
  • manual curl verification for /v1/responses streaming through VibeProxy
  • direct socket-level verification that event: and data: were emitted together after the fix
  • droid exec --model custom:gpt-5.4(xhigh) ... against the patched app, verified end-to-end success

Manual Verification Notes

Before the fix, Droid reproducibly failed on the proxy path with split SSE fragments and Unexpected EOF parsing errors.

After the fix:

  • /v1/chat/completions streams returned clean SSE data through the proxy
  • /v1/responses streams returned combined event: + data: frames instead of standalone event fragments
  • Droid completed successfully through the installed VibeProxy app using the custom OpenAI model path

@amanshresthaa amanshresthaa marked this pull request as ready for review April 3, 2026 13:03
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.

1 participant