Skip to content

fix(translator): accept CRLF and CR SSE event boundaries in passthrough parsers - #2684

Open
akdok wants to merge 1 commit into
theagentrouter:mainfrom
akdok:fix-responses-sse-crlf
Open

akdok wants to merge 1 commit into
theagentrouter:mainfrom
akdok:fix-responses-sse-crlf

Conversation

@akdok

@akdok akdok commented Sep 14, 2026

Copy link
Copy Markdown

Description

The OpenAI Responses passthrough translator (and the Speech translator, which has the identical loop) only recognised \n\n as the SSE event boundary. SSE also permits CRLF (\r\n\r\n) and CR line endings. For a compliant upstream using CRLF, the parser kept every event buffered, so the gateway never observed response.created / response.completed and lost the resolved model and token usage for metrics, access logs and tracing, even though the bytes were still forwarded to the client unchanged.

The optional space after data: mentioned in the issue is already handled on main by cutSSEDataPrefix; this PR covers the remaining line-ending half.

Changes:

  • Add cutSSEEvent in util.go: splits the buffer at the first empty line, accepting LF, CRLF or CR line endings. A trailing lone \r stays buffered because it may be the first byte of a CRLF that is split across two response body chunks.
  • Add sseLines: splits an event into lines on any of those endings, so data: lines no longer carry a trailing \r.
  • Use both helpers in openai_responses.go and openai_speech.go in place of the hard-coded \n\n / \n handling.
  • Tests: table tests for the helpers, CRLF / CR / no-space / event:-prefixed streams for the Responses parser, a CRLF boundary split across two ResponseBody calls with the \r at the chunk edge, and a CRLF case for the Speech span recorder.

The Anthropic-format stream parsers in anthropic_helper.go / openai_helper.go also split on \n\n; I left them out to keep this focused on the passthrough parsers from the issue, but happy to switch them to the same helper here or in a follow-up.

Fixes #2463

Notes

  • make precommit and go test ./internal/translator/... pass locally.
  • Per the contributing guidelines' transparency policy: this change was written with AI assistance; I understand and take full ownership of the code.

…gh parsers

The Responses API and Speech passthrough translators only recognised
"\n\n" as the SSE event boundary, so a compliant upstream using CRLF
line endings never had its response.created / response.completed events
observed by the gateway. The bytes were still forwarded to the client,
but usage metrics, access logs and tracing lost the token counts and
resolved model.

Add cutSSEEvent and sseLines helpers that split on any spec-allowed line
ending (LF, CRLF, CR), keeping a trailing lone CR buffered since it may
be the first byte of a CRLF split across chunks, and use them in both
parsers.

Signed-off-by: Kamil Dokumaci <dok@tuta.com>
@akdok
akdok requested a review from a team as a code owner September 14, 2026 02:13
@netlify

netlify Bot commented Sep 14, 2026

Copy link
Copy Markdown

Deploy Preview for theagentrouter canceled.

Name Link
🔨 Latest commit 20f4ed1
🔍 Latest deploy log https://app.netlify.com/projects/theagentrouter/deploys/6aa75841c973a9000838c59f

@missBerg missBerg added bug Something isn't working area/translation Provider/endpoint coverage and schema translation (incl. fidelity bugs) area/observability Metrics, tracing, logging, GenAI observability labels Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/observability Metrics, tracing, logging, GenAI observability area/translation Provider/endpoint coverage and schema translation (incl. fidelity bugs) bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Responses API streaming parser ignores standard SSE framing

2 participants