Repository navigation
Conversation
warelik
force-pushed
the
fix/unified-response-projection
branch
2 times, most recently
from
July 3, 2026 22:03
a27f4c9 to
691eb39
Compare
Contributor
Author
|
Code-review follow-up: |
6 tasks done
warelik
force-pushed
the
fix/unified-response-projection
branch
from
July 13, 2026 08:12
59e157e to
fc96ef8
Compare
diegosouzapw
added a commit
to diegosouzapw/OmniRoute
that referenced
this pull request
Jul 17, 2026
…nvelope (#7255) * fix(sse): project non-streaming JSON back to the Gemini/Antigravity envelope The streaming and non-streaming response paths disagreed on how a response is projected back into a non-OpenAI client's wire format. Streaming goes through the translator registry, where the FORMATS.OPENAI -> FORMATS.ANTIGRAVITY translator (open-sse/translator/response/openai-to-antigravity.ts) projects each OpenAI chunk into the `{ response: { candidates: [...] } }` envelope, mapping tool_calls to `functionCall` parts and reasoning to `thought` parts. The non-streaming path uses translateNonStreamingResponse() instead. Its "Phase 3: translate back to client source format" step only special-cased FORMATS.CLAUDE — every other non-OpenAI client format fell through and returned the raw OpenAI chat.completion intermediate. A Gemini/Antigravity client issuing a non-streaming request therefore received `choices[]`/`tool_calls` instead of `candidates[]`/`functionCall`: the client's parser sees no candidates and the function calls are effectively dropped, so tool-calling silently breaks on the JSON path while working over SSE. Adds convertOpenAINonStreamingToGeminiFamily() and wires it into Phase 3 for FORMATS.GEMINI / FORMATS.ANTIGRAVITY, mirroring the shape the streaming translator already emits so both paths agree. Tool-call `arguments` are parsed through a non-throwing helper: a provider emitting truncated JSON degrades that call's args to `{}` rather than raising an uncaught SyntaxError in the shared response hot path (matching the streaming translator's behaviour). Scoped deliberately narrow: only the Gemini-family projection gap proven by the failing test is closed. The Ollama/Responses projections and the SSE terminal tracker from the upstream change are not ported — OmniRoute has no OLLAMA format in FORMATS, and its Responses/[DONE] handling already lives in nonStreamingSse.ts + the registry. Co-authored-by: W ARELIK <warelik@users.noreply.github.com> Inspired-by: decolua/9router#2348 * chore(changelog): fragment for #7255 --------- Co-authored-by: W ARELIK <warelik@users.noreply.github.com>
Contributor
|
Fresh gap probe on 2026-07-23 at
Reproduction: node --input-type=module -e 'import { projectCompletionToClientFormat } from "./open-sse/translator/response/completionProjector.js"; const out=projectCompletionToClientFormat({id:"chatcmpl-gap",model:"resolved-model",choices:[{message:{role:"assistant",content:"ok"},finish_reason:"stop"}],usage:{prompt_tokens:12,prompt_tokens_details:{cached_tokens:3},completion_tokens:5,completion_tokens_details:{reasoning_tokens:2},total_tokens:17}},"openai-responses"); console.log(JSON.stringify({model:out.model,usage:out.usage,terminalShape:out.status}))'Observed: {"usage":{"input_tokens":12,"output_tokens":5,"total_tokens":17},"terminalShape":"completed"}Expected additions: {"model":"resolved-model","usage":{"input_tokens":12,"input_tokens_details":{"cached_tokens":3},"output_tokens":5,"output_tokens_details":{"reasoning_tokens":2},"total_tokens":17}}PR #2453 already owns native Responses SSE-to-JSON preservation for these fields. This gap is specifically the unified Chat-to-client projection implemented here, so no competing projection PR is needed if it can be covered in this branch. |
HouMinXi
pushed a commit
to HouMinXi/OmniRoute
that referenced
this pull request
Aug 2, 2026
…nvelope (diegosouzapw#7255) * fix(sse): project non-streaming JSON back to the Gemini/Antigravity envelope The streaming and non-streaming response paths disagreed on how a response is projected back into a non-OpenAI client's wire format. Streaming goes through the translator registry, where the FORMATS.OPENAI -> FORMATS.ANTIGRAVITY translator (open-sse/translator/response/openai-to-antigravity.ts) projects each OpenAI chunk into the `{ response: { candidates: [...] } }` envelope, mapping tool_calls to `functionCall` parts and reasoning to `thought` parts. The non-streaming path uses translateNonStreamingResponse() instead. Its "Phase 3: translate back to client source format" step only special-cased FORMATS.CLAUDE — every other non-OpenAI client format fell through and returned the raw OpenAI chat.completion intermediate. A Gemini/Antigravity client issuing a non-streaming request therefore received `choices[]`/`tool_calls` instead of `candidates[]`/`functionCall`: the client's parser sees no candidates and the function calls are effectively dropped, so tool-calling silently breaks on the JSON path while working over SSE. Adds convertOpenAINonStreamingToGeminiFamily() and wires it into Phase 3 for FORMATS.GEMINI / FORMATS.ANTIGRAVITY, mirroring the shape the streaming translator already emits so both paths agree. Tool-call `arguments` are parsed through a non-throwing helper: a provider emitting truncated JSON degrades that call's args to `{}` rather than raising an uncaught SyntaxError in the shared response hot path (matching the streaming translator's behaviour). Scoped deliberately narrow: only the Gemini-family projection gap proven by the failing test is closed. The Ollama/Responses projections and the SSE terminal tracker from the upstream change are not ported — OmniRoute has no OLLAMA format in FORMATS, and its Responses/[DONE] handling already lives in nonStreamingSse.ts + the registry. Co-authored-by: W ARELIK <warelik@users.noreply.github.com> Inspired-by: decolua/9router#2348 * chore(changelog): fragment for diegosouzapw#7255 --------- Co-authored-by: W ARELIK <warelik@users.noreply.github.com>
This was referenced Aug 3, 2026
golamrabbi696
added a commit
to golamrabbi696/EzRouter
that referenced
this pull request
Aug 12, 2026
…d SSE-to-JSON paths Rebase unified client-format projection onto upstream v0.5.30. Gate semantic tracing on explicit debug state, carry trace context without competing with passthrough decloaking, and forward exactly one stream terminal marker.\n\nVerification: completionProjectorSelfCheck 22/22; production build 126/126; OpenAI and Claude streaming/non-streaming smoke passed.
warelik
force-pushed
the
fix/unified-response-projection
branch
from
August 22, 2026 05:12
fc96ef8 to
a69d489
Compare
Client-keyed Codex branch and pre-projection reasoning strip broke neighboring Claude/Responses paths. Branch on targetFormat, project thoughts first, restore SSE errors and cache usage, drop HMAC tracing from this leak fix.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…nvelope (diegosouzapw#7255) * fix(sse): project non-streaming JSON back to the Gemini/Antigravity envelope The streaming and non-streaming response paths disagreed on how a response is projected back into a non-OpenAI client's wire format. Streaming goes through the translator registry, where the FORMATS.OPENAI -> FORMATS.ANTIGRAVITY translator (open-sse/translator/response/openai-to-antigravity.ts) projects each OpenAI chunk into the `{ response: { candidates: [...] } }` envelope, mapping tool_calls to `functionCall` parts and reasoning to `thought` parts. The non-streaming path uses translateNonStreamingResponse() instead. Its "Phase 3: translate back to client source format" step only special-cased FORMATS.CLAUDE — every other non-OpenAI client format fell through and returned the raw OpenAI chat.completion intermediate. A Gemini/Antigravity client issuing a non-streaming request therefore received `choices[]`/`tool_calls` instead of `candidates[]`/`functionCall`: the client's parser sees no candidates and the function calls are effectively dropped, so tool-calling silently breaks on the JSON path while working over SSE. Adds convertOpenAINonStreamingToGeminiFamily() and wires it into Phase 3 for FORMATS.GEMINI / FORMATS.ANTIGRAVITY, mirroring the shape the streaming translator already emits so both paths agree. Tool-call `arguments` are parsed through a non-throwing helper: a provider emitting truncated JSON degrades that call's args to `{}` rather than raising an uncaught SyntaxError in the shared response hot path (matching the streaming translator's behaviour). Scoped deliberately narrow: only the Gemini-family projection gap proven by the failing test is closed. The Ollama/Responses projections and the SSE terminal tracker from the upstream change are not ported — OmniRoute has no OLLAMA format in FORMATS, and its Responses/[DONE] handling already lives in nonStreamingSse.ts + the registry. Co-authored-by: W ARELIK <warelik@users.noreply.github.com> Inspired-by: decolua/9router#2348 * chore(changelog): fragment for diegosouzapw#7255 --------- Co-authored-by: W ARELIK <warelik@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2347
Related: #2046, #1396
Summary
Unifies non-streaming and forced-SSE→JSON response projection on upstream v0.5.30. Provider responses are normalized to the existing OpenAI Chat Completion pivot, then projected into the client's OpenAI, Claude, Gemini-family, Ollama, or Responses envelope. Tool calls, reasoning, finish reasons, usage, and client-native response structure are preserved instead of leaking raw OpenAI
choices[]or dropping Gemini function calls.The streaming diagnostic path emits only counts and HMAC digests. It now performs no body scans unless debug logging is explicitly enabled. Passthrough terminal handling also forwards exactly one real upstream
[DONE]and synthesizes one only when upstream omitted it.Changes
completionProjector.js: shared OpenAI→client projection and Responses→OpenAI normalization.nonStreamingHandler.js/sseToJsonHandler.js: use the shared projection contract; remove duplicate/dead projection helpers.toolSemanticsTrace.js: safe request/provider/client tool semantics tracing.src/sse/utils/logger.js: export explicit debug state so tracing is not activated merely because a logger has adebug()method.sseDoneTracker.js: pure terminal tracker; first upstream[DONE]is forwarded, duplicates are suppressed, and flush only synthesizes a missing terminal.stream.js,chatCore.js,streamingHandler.js: tracing and projection wiring. Source/target trace context is carried on the existing request logger, avoiding a positional-signature conflict with passthrough-specific features.Files
10 files: four handlers,
completionProjector.js,completionProjectorSelfCheck.mjs,toolSemanticsTrace.js,sseDoneTracker.js,stream.js, andsrc/sse/utils/logger.js.Verification
completionProjectorSelfCheck.mjs: 22/22[DONE]and one Claudemessage_stopgit apply --check/git diff --check: clean on9845a17