Fix: SSE data: [DONE] sentinel for non-streaming requests (complements PR #285)#286
Fix: SSE data: [DONE] sentinel for non-streaming requests (complements PR #285)#286rothnic wants to merge 1 commit intodecolua:masterfrom
Conversation
|
Still coming across some other issues with structured outputs and will update this PR and mark ready once I have them all addressed. |
…lity This commit fixes critical issues affecting structured JSON output: 1. **SSE data: [DONE] sentinel for non-streaming requests** Problem: 9router emitted 'data: [DONE]' even for non-streaming requests, causing AI SDK and other clients to fail JSON parsing. Root cause: AI SDK's generateObject() sends stream: undefined (not false), so stream !== false evaluates to true. Solution: Changed check from stream !== false to stream === true in: - open-sse/utils/stream.js (2 locations) - open-sse/executors/github.js (1 location) Now only emits data: [DONE] when explicitly streaming (stream: true). 2. **response_format support for Claude via GitHub Copilot** Problem: GitHub's internal translation doesn't respect OpenAI's response_format parameter. Claude returns plain text instead of JSON. Solution: Modified sanitizeMessagesForChatCompletions() in github.js to inject JSON instructions into system prompt + user message prefix. 3. **response_format support for direct Claude API** Problem: Claude doesn't understand OpenAI's response_format parameter. Solution: Modified openai-to-claude.js to translate response_format to system prompts with JSON schema instructions. Files changed: - open-sse/utils/stream.js: Fixed SSE sentinel logic - open-sse/executors/github.js: Fixed SSE + added response_format for Claude - open-sse/translator/request/openai-to-claude.js: Added response_format translation Fixes AI SDK generateObject() and all structured output clients. Closes decolua#286
CRITICAL FIX for AI SDK compatibility: **Problem:** 9router emitted 'data: [DONE]\n\n' even for non-streaming requests, causing AI SDK and other clients to fail JSON parsing. **Root Cause:** AI SDK's generateObject() sends stream: undefined (not false). The check stream !== false evaluated to true when stream was undefined, causing the SSE sentinel to be emitted incorrectly. **Solution:** Changed check from stream !== false to stream === true in: - open-sse/utils/stream.js (PASSTHROUGH mode + flush function) - open-sse/executors/github.js (TransformStream) Now data: [DONE] is ONLY emitted when explicitly streaming (stream: true). **Related:** This PR builds on decolua#285 (already merged) which added response_format support for Claude. Together these fixes enable full AI SDK structured output compatibility. Fixes AI SDK generateObject() and all structured output clients.
00826bb to
8d24d47
Compare
|
@decolua this one is ready to review now and builds on the other PR to fully resolve structured output issues I was seeing when using AISDK. In my case, I had been using AISDK within some specialized cli tools that were within a larger workflow managed by OpenCode. So the agent would use a cli tool, which uses aisdk targeting a 9router model combo i defined that preferred a few sources, where one was Kimi K2.5, then a fallback to gh copilot gpt-5-mini. I was seeing most of the requests fail, and it turned out to be multiple reasons. One being the claude response format for kimi already fixed, then the github model due gh copilot ignoring the format output and the referenced issue where stream being undefined (set by aisdk) resulted in that condition being handled incorrectly. |
|
Hi @rothnic, thanks for this fix! 🙏 We've cherry-picked the relevant parts from this PR into our fork: What we merged:
What we skipped:
Great investigation on the |
- Guard data: [DONE] in github.js TransformStream with stream === true - Inject response_format as system prompt for Claude models via GitHub executor Note: stream.js guards skipped, createSSEStream is only called for true streaming paths. Cherry-picked and adapted from PR #286 by @rothnic #286 Made-with: Cursor
Summary
This PR fixes a critical SSE issue that was blocking AI SDK structured output compatibility. It complements the
response_formatfix from PR #285 (already merged).Relation to PR #285
PR #285 (merged): Added
response_formatsupport for Claude by translating OpenAI'sresponse_formatparameter to Claude-compatible system prompts inopenai-to-claude.js.This PR (#286): Fixes the SSE
data: [DONE]sentinel issue that was still causing AI SDK to fail even with the response_format fix in place.Both fixes are required for full AI SDK
generateObject()compatibility.The Problem
9router emitted
data: [DONE]\n\neven for non-streaming requests, causing AI SDK and other clients to fail JSON parsing with "Invalid JSON response" errors.Root Cause Discovery
AI SDK's
generateObject()sendsstream: undefined(notstream: false). The original checkstream !== falseevaluated totruewhenstreamwasundefined, incorrectly triggering the SSE sentinel emission.Solution
Changed the check from
stream !== falsetostream === truein:open-sse/utils/stream.js(PASSTHROUGH mode + flush function)open-sse/executors/github.js(TransformStream)Now
data: [DONE]is ONLY emitted when explicitly streaming (stream: true).Testing
Verified working with:
generateObject()with gh/gpt-4ogenerateObject()with gh/claude-sonnet-4.5stream: falseFiles Changed
open-sse/utils/stream.js- Fixed SSE sentinel logic (2 locations)open-sse/executors/github.js- Fixed SSE sentinel in TransformStreamImpact
Fixes compatibility with:
generateObject,generateTextwith schema)stream: trueNote on Merge Conflicts
This PR was rebased on top of PR #285 (response_format support). The conflicts were due to overlapping changes - PR #285 added the
response_formatsupport inopenai-to-claude.js, while this PR focuses specifically on the SSEdata: [DONE]issue. This PR now only includes the SSE fixes to avoid duplication.