Skip to content

fix: treat DONE-less SSE close after finish_reason as clean EOF (#76)#77

Merged
yuanhao merged 1 commit into
mainfrom
fix/streamended-after-finish-reason
Jul 20, 2026
Merged

fix: treat DONE-less SSE close after finish_reason as clean EOF (#76)#77
yuanhao merged 1 commit into
mainfrom
fix/streamended-after-finish-reason

Conversation

@yuanhao

@yuanhao yuanhao commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Fixes #76 — implements exactly the guarded fix proposed there, verified against main first (diagnosis was line-accurate: the None arm tolerated the close, the StreamEnded arm fell through classify_eventsource_error's catch-all).

  • saw_finish_reason tracked in the compat read loop; Some(Err(StreamEnded)) with the flag set → break (same path as graceful None)
  • Without the flag → unchanged error path (genuine truncation still surfaces and retries)
  • New tests/openai_compat_stream_test.rs (wiremock, MiniMax-shaped config): DONE-less close after finish_reason → clean completion, accumulated content, StopReason::Stop; close before any finish_reason → still Err
  • CHANGELOG under Unreleased

Field report: yologdev/yoyo-evolve#612 (yoyo can then drop its cosmetic mitigation 1bb04f1e after the next yoagent release).

Test plan

  • 2 new wiremock tests (both directions of the guard)
  • Full suite green under --all-features; clippy -Dwarnings clean

🤖 Generated with Claude Code

Some providers (MiniMax confirmed in the field, yoyo-evolve#612) close
the SSE connection without the OpenAI-standard `data: [DONE]`
terminator, surfacing as reqwest_eventsource::Error::StreamEnded. The
compat reader already tolerated the identical close when it arrived as
a graceful None, but errored on StreamEnded — after the complete
response had already streamed to the caller.

Guard: track saw_finish_reason; StreamEnded after a finish_reason is a
completed response (break, clean finish); StreamEnded with NO
finish_reason is genuine mid-stream truncation and stays an error, so
retry semantics remain honest for real drops.

Tests: wiremock SSE with deltas + finish_reason then body end without
[DONE] → clean completion with accumulated content and StopReason::Stop;
sibling close before any finish_reason → still Err.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@yuanhao
yuanhao merged commit 5004937 into main Jul 20, 2026
5 checks passed
@yuanhao
yuanhao deleted the fix/streamended-after-finish-reason branch July 20, 2026 22:35
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.

openai_compat: StreamEnded after finish_reason should be a clean EOF, not ProviderError (MiniMax closes without [DONE])

1 participant