Skip to content

OpenAI Chat SSE parser ignores valid data:{...} frames without a space #1170

Description

@Aono255

Client or integration

Other — downstream application embedding the OpenCodex proxy.

Provider or upstream service

Private OpenAI-compatible model gateway (provider identity and endpoint redacted).

OpenCodex version

@bitkyc08/opencodex 2.7.42

Endpoint or capability

/v1/chat/completions streaming (SSE)

Current behaviour

The OpenAI Chat adapter only processes lines starting with data: (a colon followed by a space).

The upstream emits valid SSE data fields without that optional space, for example data:{...}. As a result, every JSON frame is ignored. A terminal chunk carrying finish_reason: "stop" and a trailing usage-only chunk are also ignored. When the upstream closes the stream, OpenCodex reports:

upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation

Expected behaviour

OpenCodex should accept both data:{...} and data: {...} as SSE data fields.

A non-empty OpenAI finish_reason should remain a valid terminal signal even when the upstream omits the OpenAI-specific data: [DONE] sentinel.

Minimal redacted request or reproduction

curl -N -sS "$BASE_URL/chat/completions" \
  -H "Content-Type: application/json" \
  -H "X-Example-Auth: <redacted>" \
  -d '{
    "model": "<model>",
    "messages": [{"role": "user", "content": "hello"}],
    "stream": true
  }'

The upstream response uses this framing pattern:

data:{"id":"<id>","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"..."},"finish_reason":null}]}

...

data:{"id":"<id>","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data:{"id":"<id>","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}

There is no data: [DONE] frame.

Actual response or error

MODEL_RUNTIME_CANARY_FAILED: turn status failed; stream disconnected before completion:
upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation

Upstream documentation

SSE field syntax permits the value to begin immediately after the colon; one optional U+0020 space is stripped when present:

https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation

The non-null finish_reason behavior is an OpenAI-compatible streaming convention. The upstream has no public provider-specific specification.

Suggested mapping or implementation notes

The shared data-line handler can accept the data: field name regardless of whether a space follows it:

if (!line.startsWith("data:")) return "continue";
const payload = line.slice("data:".length).trim();

Regression coverage should include:

  1. Unspaced data:{...} frames with a final non-null finish_reason and no [DONE] -> successful completion.
  2. Standard data: {...} framing -> unchanged behavior.
  3. A genuinely truncated stream with no [DONE], no final finish_reason, and no terminal usage frame -> still fail closed.

Additional context and attachments

The report is fully redacted. No production endpoint, model identifier, request content, or credentials are included.

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportsstreamingSSE, WebSocket, terminal stream frames

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions