Skip to content

Session aborts with InvalidProviderOutput when SSE streams mix LF and CRLF line endings #54102

Closed

Description

@FainBleeze

Description

Streaming responses from an OpenAI-compatible provider intermittently abort the step with:

AI.Error.InvalidProviderOutput: Invalid <provider>/openai-compatible-chat stream event
  [cause]: SchemaError: Expected a valid JSON string

Root cause looks like Effect's SSE parser (pinned at [email protected]) mishandling a stream that mixes line terminators. The provider emits LF-terminated data: {...}\n\n events but CRLF-terminated keepalives : ping - <ts>\r\n\r\n. After a \r\n\r\n blank line the parser drops/merges the following events, so a frame arrives whose data is not valid JSON and the session fails ("Failed to drain Session").

Upstream Effect main appears to already fix this: the parser moved to packages/effect/src/encoding/Sse.ts, the inner scan starts at position + startingPosition, and discardTrailingNewline is parser state instead of a per-feed local.

Request: bump the pinned effect catalog version (root package.json -> 4.0.0-rc.112) to a release that includes the fix, and adjust packages/ai/src/route/framing.ts for the new feed signature (it now returns SseError | undefined).

Plugins

none

OpenCode version

v2.0.26

Steps to reproduce

  1. Configure an OpenAI-compatible provider whose gateway mixes LF-terminated data events (data: {...}\n\n) with CRLF-terminated keepalives (: ping - <ts>\r\n\r\n).
  2. Start a session with a long streamed tool-call / response.
  3. It aborts intermittently with the error above.

Minimal parser-level repro against the pinned parser (effect/unstable/encoding/Sse):

import { makeParser } from "effect/unstable/encoding/Sse"
const collect = (s) => {
  const out = []
  const p = makeParser((e) => { if (e._tag === "Event") out.push(e.data) })
  p.feed(s)
  return out
}
collect('data: a\n\n: c\r\n\r\ndata: b\n\n') // => ["a"]      (b lost)
collect('data: a\r\n\r\ndata: b\n\n')          // => ["a"]      (b lost)
collect('data: a\n\ndata: b\n\n')              // => ["a","b"]  (ok)
collect('data: a\r\n\r\ndata: b\r\n\r\n')    // => ["a","b"]  (ok)

On a real 915 KB captured response the parser emits 1266 events instead of the expected 3234.

Screenshot and/or share link

No response

Operating System

Windows 11 (Windows_NT 10.0.26200, x64)

Terminal

Windows Terminal (xterm-256color, cmd.exe)

Activity

  1. opencode-agent commented on Oct 9, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report and the parser-level repro, which made this quick to confirm.

    I reproduced it on v2.0.26. I streamed an OpenAI-compatible chat response through OpenCode's SSE framing, with LF-terminated data: events and a CRLF-terminated : ping - N keepalive after each one. When the body arrives whole or in larger chunks (512 or 1024 bytes), events after the CRLF keepalives are lost, and the request fails with OpenAI Chat stream ended without finish_reason. That is the same root cause as your InvalidProviderOutput error. Your makeParser cases also fail the same way with [email protected].

    On the latest v2 branch (2f3ba04a), the same test passes in every chunking. That branch has already moved to [email protected] (#50231), whose SSE parser handles mixed line endings, and framing.ts already uses the new effect/encoding/Sse module. So this is likely already fixed, and the fix should be in the next release (it is not in 2.0.26).

    The bot will close this issue in 1 day unless you reply that it still happens, for example on a newer version.

  2. rekram1-node commented on Oct 9, 2026

    @rekram1-node
    Collaborator

    1aae67e

    fixed in next release

  3. opencode-agent commented on Oct 10, 2026

    @opencode-agent
    Contributor

    Closing this as completed, since we haven't heard back. I reproduced the mixed LF/CRLF problem on v2.0.26: events after the CRLF : ping keepalives were lost and the request failed. On the latest v2 branch (2f3ba04a) every case worked, because that branch moved to [email protected] (#50231). The fix should be in the next release.

    If it still happens once you're on a newer version, please comment here or open a new issue and mention this one. Thanks again for the detailed report and the parser repro!

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions