Skip to content

fix(openai): process custom responses stream chunks - #4497

Open
eyeveil wants to merge 1 commit into
maximhq:devfrom
eyeveil:fix/openai-responses-stream-custom-handler
Open

fix(openai): process custom responses stream chunks#4497
eyeveil wants to merge 1 commit into
maximhq:devfrom
eyeveil:fix/openai-responses-stream-custom-handler

Conversation

@eyeveil

@eyeveil eyeveil commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes HandleOpenAIResponsesStreaming when a customResponseHandler is provided.

Previously, the custom handler branch parsed the SSE payload but skipped the normal Responses stream post-processing path. That meant successful custom-handled Responses stream chunks were never emitted. The branch also passed nil, false, false into the handler, so custom raw request/response handling could not work correctly.

Changes

  • Pass the serialized request body and raw-field flags into customResponseHandler.
  • Share the normal Responses stream post-processing for both default and custom handler paths:
    • post response conversion
    • raw response propagation
    • response.error / response.failed conversion
    • terminal completed/incomplete chunk handling
    • chunk latency and index metadata
  • Preserve custom raw request/response metadata on emitted stream errors after EnrichError runs.
  • Add regression coverage for:
    • custom handler emitting a completed Responses stream chunk
    • custom handler emitting a response.error stream chunk

Verification

cd core
go test ./providers/openai/... -count=1
go vet ./providers/openai/...

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@nnNyx, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6991c98c-e19a-4c05-824b-f35524eccf20

📥 Commits

Reviewing files that changed from the base of the PR and between a0ec4f5 and 47c3fcd.

📒 Files selected for processing (2)
  • core/providers/openai/openai.go
  • core/providers/openai/responsesstream_test.go
📝 Walkthrough

Walkthrough

Refactors HandleOpenAIResponsesStreaming to preserve raw request and response data through custom-handler, error, failed, and completion paths, and adds tests that verify the streamed chunk and error outputs keep those raw fields.

Changes

Responses Streaming Raw Field Propagation

Layer / File(s) Summary
SSE event loop: raw field capture and custom handler wiring
core/providers/openai/openai.go
Adds per-event rawRequest, rawResponse, and bifrostErr handling, passes real raw flags into customResponseHandler, preserves custom raw fields on handler errors, applies postResponseConverter, converts error and failed response types into BifrostError chunks, and standardizes final/non-final chunk emission with chunk index and latency updates.
Unit tests: chunk completion and error-event raw field assertions
core/providers/openai/responsesstream_test.go
Adds tests for completed and error SSE events under a custom handler, asserting raw field propagation, BifrostError contents, chunk sequencing, and stream closure, plus a no-op logger stub.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • maximhq/bifrost#3956: Also touches OpenAI Responses streaming behavior in core/providers/openai/openai.go, including SSE stream handling around non-standard response bodies.
  • maximhq/bifrost#4418: Also changes HandleOpenAIResponsesStreaming error-path behavior for OpenAI responses streaming.

Suggested reviewers: danpiths, akshaydeo, roroghost17

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly summarizes the main bug fix in the OpenAI Responses streaming path.
Description check ✅ Passed It includes the required Summary, Changes, and Verification sections with clear details, though several template sections are omitted.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai
coderabbitai Bot requested review from akshaydeo and danpiths June 17, 2026 13:20
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

The change is safe to merge; it closes a longstanding correctness gap in the Responses stream custom-handler path without touching any shared infrastructure.

The unification of the custom and default post-processing paths is mechanically straightforward and well-covered by the new unit tests. The preserveCustomRawErrorFields closure correctly restores custom raw fields after EnrichError on every error branch. The only gap is a test for the direct-bifrostErr-return path of the custom handler, but the production logic there is simple and the closure behaviour is identical to the paths that are tested.

No files require special attention; both changed files are self-contained within the OpenAI provider package.

Important Files Changed

Filename Overview
core/providers/openai/openai.go Unifies the custom-handler and default paths in HandleOpenAIResponsesStreaming; correct propagation of jsonBody and raw flags to customResponseHandler, and preserveCustomRawErrorFields closure correctly restores custom raw fields after EnrichError on all error paths.
core/providers/openai/responsesstream_test.go New test file covering the two regression scenarios; uses an in-memory fasthttp listener to avoid network dependencies, correctly placed in the internal package to access unexported symbols.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[SSE chunk read] --> B{customResponseHandler set?}
    B -- Yes --> C["customResponseHandler(jsonData, &response, jsonBody, sendBackRawRequest, sendBackRawResponse)"]
    C --> D{bifrostErr != nil?}
    D -- Yes --> E["preserveCustomRawErrorFields(EnrichError(bifrostErr, jsonBody, nil, ...))"]
    E --> F[ProcessAndSendBifrostError → return]
    D -- No --> G[rawRequest, rawResponse captured]
    B -- No --> H["sonic.UnmarshalString(jsonData, &response)"]
    H --> I{parse error?}
    I -- Yes --> J[log warn, continue]
    I -- No --> G
    G --> K{postResponseConverter set?}
    K -- Yes --> L[Convert response]
    K -- No --> M
    L --> M{sendBackRawResponse?}
    M -- Yes --> N{"rawResponse != nil?"}
    N -- Yes --> O[response.ExtraFields.RawResponse = rawResponse]
    N -- No --> P[response.ExtraFields.RawResponse = jsonData]
    O --> Q
    P --> Q{response.Type == Error?}
    M -- No --> Q
    Q -- Yes --> R["Build bifrostErr from response fields, EnrichError(bifrostErr, jsonBody, jsonData, ...)"]
    R --> S["preserveCustomRawErrorFields(bifrostErr)"]
    S --> T[ProcessAndSendBifrostError → return]
    Q -- No --> U{response.Type == Failed?}
    U -- Yes --> V["Build bifrostErr, EnrichError, preserveCustomRawErrorFields → return"]
    U -- No --> W["ChunkIndex = SequenceNumber"]
    W --> X{Completed or Incomplete?}
    X -- Yes --> Y{sendBackRawRequest?}
    Y -- Yes --> Z{"rawRequest != nil?"}
    Z -- Yes --> AA[response.ExtraFields.RawRequest = rawRequest]
    Z -- No --> AB[ParseAndSetRawRequest from jsonBody]
    AA --> AC[Latency=total, send terminal chunk → return]
    AB --> AC
    Y -- No --> AC
    X -- No --> AD[Latency=delta, emit chunk, continue]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[SSE chunk read] --> B{customResponseHandler set?}
    B -- Yes --> C["customResponseHandler(jsonData, &response, jsonBody, sendBackRawRequest, sendBackRawResponse)"]
    C --> D{bifrostErr != nil?}
    D -- Yes --> E["preserveCustomRawErrorFields(EnrichError(bifrostErr, jsonBody, nil, ...))"]
    E --> F[ProcessAndSendBifrostError → return]
    D -- No --> G[rawRequest, rawResponse captured]
    B -- No --> H["sonic.UnmarshalString(jsonData, &response)"]
    H --> I{parse error?}
    I -- Yes --> J[log warn, continue]
    I -- No --> G
    G --> K{postResponseConverter set?}
    K -- Yes --> L[Convert response]
    K -- No --> M
    L --> M{sendBackRawResponse?}
    M -- Yes --> N{"rawResponse != nil?"}
    N -- Yes --> O[response.ExtraFields.RawResponse = rawResponse]
    N -- No --> P[response.ExtraFields.RawResponse = jsonData]
    O --> Q
    P --> Q{response.Type == Error?}
    M -- No --> Q
    Q -- Yes --> R["Build bifrostErr from response fields, EnrichError(bifrostErr, jsonBody, jsonData, ...)"]
    R --> S["preserveCustomRawErrorFields(bifrostErr)"]
    S --> T[ProcessAndSendBifrostError → return]
    Q -- No --> U{response.Type == Failed?}
    U -- Yes --> V["Build bifrostErr, EnrichError, preserveCustomRawErrorFields → return"]
    U -- No --> W["ChunkIndex = SequenceNumber"]
    W --> X{Completed or Incomplete?}
    X -- Yes --> Y{sendBackRawRequest?}
    Y -- Yes --> Z{"rawRequest != nil?"}
    Z -- Yes --> AA[response.ExtraFields.RawRequest = rawRequest]
    Z -- No --> AB[ParseAndSetRawRequest from jsonBody]
    AA --> AC[Latency=total, send terminal chunk → return]
    AB --> AC
    Y -- No --> AC
    X -- No --> AD[Latency=delta, emit chunk, continue]
Loading

Reviews (3): Last reviewed commit: "fix(openai): process custom responses st..." | Re-trigger Greptile

Comment thread core/providers/openai/openai.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/providers/openai/openai.go (1)

1794-1802: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid nil-clobbering custom raw fields on handler errors.

If customResponseHandler attaches RawRequest/RawResponse directly to bifrostErr.ExtraFields but returns nil raw values, Lines 1795-1800 overwrite those fields before enrichment. Preserve existing error metadata and only prefer non-nil returned raw values.

🐛 Proposed fix
 				if bifrostErr != nil {
-					if sendBackRawRequest {
-						bifrostErr.ExtraFields.RawRequest = rawRequest
-					}
-					if sendBackRawResponse {
-						bifrostErr.ExtraFields.RawResponse = rawResponse
-					}
+					customRawRequest := bifrostErr.ExtraFields.RawRequest
+					if rawRequest != nil {
+						customRawRequest = rawRequest
+					}
+					customRawResponse := bifrostErr.ExtraFields.RawResponse
+					if rawResponse != nil {
+						customRawResponse = rawResponse
+					}
 					ctx.SetValue(schemas.BifrostContextKeyStreamEndIndicator, true)
-					providerUtils.ProcessAndSendBifrostError(ctx, postHookRunner, preserveCustomRawErrorFields(providerUtils.EnrichError(ctx, bifrostErr, jsonBody, nil, sendBackRawRequest, sendBackRawResponse)), responseChan, logger, postHookSpanFinalizer)
+					enrichedErr := providerUtils.EnrichError(ctx, bifrostErr, jsonBody, nil, sendBackRawRequest, sendBackRawResponse)
+					if sendBackRawRequest && customRawRequest != nil {
+						enrichedErr.ExtraFields.RawRequest = customRawRequest
+					}
+					if sendBackRawResponse && customRawResponse != nil {
+						enrichedErr.ExtraFields.RawResponse = customRawResponse
+					}
+					providerUtils.ProcessAndSendBifrostError(ctx, postHookRunner, enrichedErr, responseChan, logger, postHookSpanFinalizer)
 					return
 				}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/providers/openai/openai.go` around lines 1794 - 1802, When processing
bifrostErr and the sendBackRawRequest or sendBackRawResponse flags are true, the
code unconditionally overwrites bifrostErr.ExtraFields.RawRequest and
bifrostErr.ExtraFields.RawResponse with the returned rawRequest and rawResponse
values, even if those returned values are nil. This clobbers any custom raw
fields that customResponseHandler may have already attached to
bifrostErr.ExtraFields. Fix this by only setting RawRequest and RawResponse if
the corresponding returned values are not nil, ensuring that custom values set
by customResponseHandler are preserved while still honoring non-nil returned raw
values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@core/providers/openai/openai.go`:
- Around line 1794-1802: When processing bifrostErr and the sendBackRawRequest
or sendBackRawResponse flags are true, the code unconditionally overwrites
bifrostErr.ExtraFields.RawRequest and bifrostErr.ExtraFields.RawResponse with
the returned rawRequest and rawResponse values, even if those returned values
are nil. This clobbers any custom raw fields that customResponseHandler may have
already attached to bifrostErr.ExtraFields. Fix this by only setting RawRequest
and RawResponse if the corresponding returned values are not nil, ensuring that
custom values set by customResponseHandler are preserved while still honoring
non-nil returned raw values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d3c364e-1990-44e4-bbe3-afeb3981378d

📥 Commits

Reviewing files that changed from the base of the PR and between 8806020 and 71f4a24.

📒 Files selected for processing (2)
  • core/providers/openai/openai.go
  • core/providers/openai/responsesstream_test.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 17, 2026
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review June 18, 2026 12:09

The merge-base changed after approval.

@akshaydeo
akshaydeo requested a review from a team as a code owner June 18, 2026 12:09
@akshaydeo
akshaydeo force-pushed the dev branch 2 times, most recently from fa15f50 to ca190fc Compare June 21, 2026 11:44
@CLAassistant

CLAassistant commented Jun 23, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@akshaydeo
akshaydeo force-pushed the dev branch 2 times, most recently from ac30a53 to 7c66b20 Compare July 1, 2026 12:24
@eyeveil

eyeveil commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

recheck

@coderabbitai
coderabbitai Bot requested a review from roroghost17 July 4, 2026 08:21
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 4, 2026
@eyeveil
eyeveil force-pushed the fix/openai-responses-stream-custom-handler branch from a0ec4f5 to 47c3fcd Compare July 4, 2026 08:28
@akshaydeo
akshaydeo force-pushed the dev branch 2 times, most recently from 44564de to 493bff0 Compare July 18, 2026 01:10
@akshaydeo
akshaydeo force-pushed the dev branch 3 times, most recently from 244a01d to ce1b2a6 Compare August 13, 2026 09:47
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.

2 participants