Skip to content

fix(ai): ignore bare null SSE frames - #50793

Merged
rekram1-node merged 1 commit into
v2from
sse-null-frames
Sep 23, 2026
Merged

rekram1-node merged 1 commit into
v2from
sse-null-frames

Conversation

@rekram1-node

Copy link
Copy Markdown
Collaborator

Closes #50314

Problem

Some OpenAI-compatible proxies serialize an empty flush as a bare data: null SSE frame — mid-stream between real events, or trailing after [DONE]. This has been observed on both Chat Completions and Responses endpoints.

sseFraming only drops empty frames and [DONE], so "null" reaches the protocol decoder, fails the event schema (every protocol's event is a struct), and aborts the whole step:

Invalid deepseek/openai-compatible-chat stream event   body="null"

Change

packages/ai/src/protocols/shared.ts — sseFraming now drops a frame whose data is exactly null, beside the existing empty-frame filter.

  • Framing, not protocol. The frame carries no event for any protocol, and the same proxies front Chat and Responses endpoints. Filtering once in shared framing covers every SSE route (Framing.sse, sseWithDone, sseEvents) instead of patching each parser separately.
  • Literal null only. That is the only shape seen in the wild. Other non-object JSON (0, "null", []) and malformed objects still reach the decoder and fail loudly with the offending frame attached, so a misconfigured endpoint is still diagnosed immediately.
  • No extra parse. A string comparison on the hot path, same as [DONE]; framing still never parses JSON.

Tests

  • test/schema.test.ts — sseFraming drops data: null (plain, named-event, and after [DONE]) while keeping "null", 0, and real payloads.
  • test/provider/openai-compatible-chat.test.ts — null frames interleaved between Chat deltas produce the full text and a clean stop.
  • test/provider/openai-compatible-responses.test.ts — null frames interleaved between and after Responses events produce the full text and a finish.

All three fail without the change and pass with it. bun test in packages/ai: 1436 pass, 0 fail. bun run check: 35/35.

Some OpenAI-compatible proxies serialize an empty flush as a bare
`data: null` frame, between events or after [DONE]. The frame reached
the protocol decoder, failed the event schema, and aborted the whole
step with "Invalid <route> stream event".

Drop it in the shared SSE framing next to the existing empty-frame
filter. No protocol has a null event, so this is lossless for Chat,
Responses, Anthropic Messages, and Gemini alike. Every other payload,
including other non-object JSON and malformed objects, still reaches
the decoder and fails loudly.
@rekram1-node
rekram1-node merged commit 8656838 into v2 Sep 23, 2026
10 checks passed
@rekram1-node
rekram1-node deleted the sse-null-frames branch September 23, 2026 02:23
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