Skip to content

fix: preserve OpenAI responses stream metadata - #3528

Merged
akshaydeo merged 1 commit into
maximhq:devfrom
AI-UN:fix/minimal-fix-reasoning-summary-loss
May 15, 2026
Merged

fix: preserve OpenAI responses stream metadata#3528
akshaydeo merged 1 commit into
maximhq:devfrom
AI-UN:fix/minimal-fix-reasoning-summary-loss

Conversation

@etnperlong

Copy link
Copy Markdown
Contributor

Summary

Preserve missing OpenAI Responses stream metadata so Bifrost does not drop reasoning-summary and multi-phase message fields during normal streaming.

Changes

  • add summary_index and obfuscation to BifrostResponsesStreamResponse
  • add phase to ResponsesMessage
  • preserve these fields in WithDefaults() and streaming deep-copy helpers
  • add regression tests covering unmarshal, defaulting, and marshal paths

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./core/schemas
go test ./framework/streaming

Expected outcome: both test commands pass, and the regression tests confirm summary_index, obfuscation, and phase survive unmarshal, defaulting, deep-copy, and re-serialization.

Screenshots/Recordings

N/A

Breaking changes

  • Yes
  • No

Related issues

Related to reasoning summary metadata loss in OpenAI Responses streaming.

Security considerations

None.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d1075ad-59cb-4cb6-8452-b673abed0eae

📥 Commits

Reviewing files that changed from the base of the PR and between 171c9a7 and e65987a.

📒 Files selected for processing (3)
  • core/schemas/responses.go
  • core/schemas/responses_test.go
  • framework/streaming/responses.go

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Messages now support an optional phase field
    • Stream responses now include optional summary index and obfuscation metadata fields
    • Enhanced preservation of optional fields throughout serialization operations
  • Tests

    • Added comprehensive test coverage for new response fields, verifying proper retention across all serialization workflows

Walkthrough

This PR extends response schema structs with optional OpenAI metadata fields (phase, summary_index, obfuscation), ensures they persist through serialization cycles via updated defaults, and synchronizes deep-copy helpers to prevent accidental data mutation between instances.

Changes

OpenAI metadata field support

Layer / File(s) Summary
Schema field definitions and defaults
core/schemas/responses.go
ResponsesMessage gains optional phase field; BifrostResponsesStreamResponse gains summary_index and obfuscation fields. WithDefaults() updated to copy new fields from source into result.
Serialization test coverage
core/schemas/responses_test.go
Tests verify that summary_index, obfuscation, and phase fields survive unmarshal, defaults application, and sorted JSON marshalling, including when wrapped in stream chunks.
Deep-copy helper synchronization
framework/streaming/responses.go
deepCopyResponsesStreamResponse now clones SummaryIndex, Signature, and Obfuscation; deepCopyResponsesMessage now clones Status and Phase pointer fields.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • akshaydeo

Poem

A rabbit hops through schemas bright,
With new fields shining in the light—
phase and index, obfuscation too,
All copied deep, both old and new! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: preserve OpenAI responses stream metadata' directly and clearly describes the main change—preserving missing metadata fields in OpenAI stream responses.
Description check ✅ Passed The description includes all required template sections: summary, changes, type of change, affected areas, how to test, breaking changes, and checklist. Tests were added, and non-applicable sections are properly marked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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 and usage tips.

@coderabbitai
coderabbitai Bot requested a review from akshaydeo May 15, 2026 13:40
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

Safe to merge; all three new fields are correctly wired through struct definition, WithDefaults(), and deep-copy. The one gap is that SummaryIndex is now stored but not yet used to route multi-summary deltas during accumulation.

The struct additions, WithDefaults() propagation, and deep-copy branches are all consistent and tested. The only open question is whether multi-summary streaming responses (multiple distinct SummaryIndex values) would produce correct accumulated output — the accumulator still merges everything into Summary[0]. This is a noted pre-existing limitation rather than a regression, but it means the newly captured SummaryIndex has no behavioral effect yet on the accumulation path.

framework/streaming/responses.go — the reasoning summary accumulation loop does not use SummaryIndex to route deltas to the correct summary slot.

Important Files Changed

Filename Overview
core/schemas/responses.go Adds SummaryIndex, Obfuscation to BifrostResponsesStreamResponse and Phase to ResponsesMessage; updates WithDefaults() to carry them through. Changes are minimal and correct.
core/schemas/responses_test.go New regression test file covering unmarshal, WithDefaults(), and re-serialization paths for the three new fields. Tests are well-structured and use real JSON payloads.
framework/streaming/responses.go Adds deep-copy branches for SummaryIndex, Obfuscation, Signature, Status, and Phase; accumulation logic in appendReasoningDeltaToResponsesMessage does not yet use SummaryIndex to route multi-summary deltas.

Comments Outside Diff (1)

  1. framework/streaming/responses.go, line 577-619 (link)

    P2 SummaryIndex captured but not used for delta routing

    SummaryIndex is now correctly preserved in the struct and deep-copied, but appendReasoningDeltaToResponsesMessage still hard-codes accumulation into Summary[0] regardless of the incoming index. If OpenAI emits deltas interleaved across two summary entries (e.g. summary_index=0 and summary_index=1), the second entry's text will be silently concatenated into Summary[0], producing a garbled merged summary instead of two separate entries. The resp.SummaryIndex value on the chunk is available at the call site (line 612) but is never threaded into the append helper.

Reviews (1): Last reviewed commit: "fix: preserve OpenAI responses stream me..." | Re-trigger Greptile

@akshaydeo

Copy link
Copy Markdown
Contributor

❤️ for the PR @etnperlong

@akshaydeo
akshaydeo merged commit c4517e9 into maximhq:dev May 15, 2026
3 of 4 checks passed
akshaydeo added a commit that referenced this pull request May 15, 2026
## Summary

Several fields on the Responses streaming schema (`Phase`, `SummaryIndex`, `Obfuscation`, and the latent leaks `Status` and `Signature`) were either undocumented or silently dropped by the deep-copy helper used during stream processing. This PR fixes the documentation, ensures the deep-copy preserves all of these fields with proper pointer independence, and adds test coverage to prevent regressions.

The `Phase` field is particularly important: it is required when replaying history to `gpt-5.3-codex+` models, and omitting it causes significant performance degradation.

## Changes

- Expanded the inline doc comment on `ResponsesMessage.Phase` to explain its role (`"commentary"` vs `"final_answer"`), its requirement on `gpt-5.3-codex+` history replay, and the performance impact of dropping it.
- Expanded the inline doc comment on `BifrostResponsesStreamResponse.SummaryIndex` to clarify which streaming event types emit it.
- Expanded the inline doc comment on `BifrostResponsesStreamResponse.Obfuscation` to explain it is random padding used as a side-channel mitigation, toggled via `StreamOptions.IncludeObfuscation`.
- Added `TestDeepCopyResponsesStreamResponsePreservesAllFields` to guard the deep-copy helper against silently dropping `Phase`, `SummaryIndex`, `Obfuscation`, `Status`, and `Signature`, and to assert pointer independence after copying.
- Added four new E2E harness cases covering: reasoning `summary_index` and `obfuscation` surviving a stream, `phase` appearing on assistant message items in a stream, and `phase` round-tripping correctly as an input field.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./framework/streaming/... -run TestDeepCopyResponsesStreamResponsePreservesAllFields -v
```

For E2E validation, run the updated provider harness collection against a live environment with a valid `openaiKey` and confirm:
- `summary_index` and `obfuscation` appear in the SSE body for the `o3-mini` streaming request.
- `phase` (`"final_answer"` or `"commentary"`) appears on assistant message items for the `gpt-5.3-codex` streaming request.
- The `phase` input round-trip request returns output items without an `unknown field "phase"` error.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes #3528

## Security considerations

The `Obfuscation` field is explicitly documented as random padding added to normalize delta event payload sizes as a side-channel mitigation. No new secrets, PII, or auth surfaces are introduced.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
akshaydeo added a commit that referenced this pull request May 15, 2026
## Summary

Several fields on the Responses streaming schema (`Phase`, `SummaryIndex`, `Obfuscation`, and the latent leaks `Status` and `Signature`) were either undocumented or silently dropped by the deep-copy helper used during stream processing. This PR fixes the documentation, ensures the deep-copy preserves all of these fields with proper pointer independence, and adds test coverage to prevent regressions.

The `Phase` field is particularly important: it is required when replaying history to `gpt-5.3-codex+` models, and omitting it causes significant performance degradation.

## Changes

- Expanded the inline doc comment on `ResponsesMessage.Phase` to explain its role (`"commentary"` vs `"final_answer"`), its requirement on `gpt-5.3-codex+` history replay, and the performance impact of dropping it.
- Expanded the inline doc comment on `BifrostResponsesStreamResponse.SummaryIndex` to clarify which streaming event types emit it.
- Expanded the inline doc comment on `BifrostResponsesStreamResponse.Obfuscation` to explain it is random padding used as a side-channel mitigation, toggled via `StreamOptions.IncludeObfuscation`.
- Added `TestDeepCopyResponsesStreamResponsePreservesAllFields` to guard the deep-copy helper against silently dropping `Phase`, `SummaryIndex`, `Obfuscation`, `Status`, and `Signature`, and to assert pointer independence after copying.
- Added four new E2E harness cases covering: reasoning `summary_index` and `obfuscation` surviving a stream, `phase` appearing on assistant message items in a stream, and `phase` round-tripping correctly as an input field.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./framework/streaming/... -run TestDeepCopyResponsesStreamResponsePreservesAllFields -v
```

For E2E validation, run the updated provider harness collection against a live environment with a valid `openaiKey` and confirm:
- `summary_index` and `obfuscation` appear in the SSE body for the `o3-mini` streaming request.
- `phase` (`"final_answer"` or `"commentary"`) appears on assistant message items for the `gpt-5.3-codex` streaming request.
- The `phase` input round-trip request returns output items without an `unknown field "phase"` error.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes #3528

## Security considerations

The `Obfuscation` field is explicitly documented as random padding added to normalize delta event payload sizes as a side-channel mitigation. No new secrets, PII, or auth surfaces are introduced.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
akshaydeo added a commit that referenced this pull request May 20, 2026
## Summary

Several fields on the Responses streaming schema (`Phase`, `SummaryIndex`, `Obfuscation`, and the latent leaks `Status` and `Signature`) were either undocumented or silently dropped by the deep-copy helper used during stream processing. This PR fixes the documentation, ensures the deep-copy preserves all of these fields with proper pointer independence, and adds test coverage to prevent regressions.

The `Phase` field is particularly important: it is required when replaying history to `gpt-5.3-codex+` models, and omitting it causes significant performance degradation.

## Changes

- Expanded the inline doc comment on `ResponsesMessage.Phase` to explain its role (`"commentary"` vs `"final_answer"`), its requirement on `gpt-5.3-codex+` history replay, and the performance impact of dropping it.
- Expanded the inline doc comment on `BifrostResponsesStreamResponse.SummaryIndex` to clarify which streaming event types emit it.
- Expanded the inline doc comment on `BifrostResponsesStreamResponse.Obfuscation` to explain it is random padding used as a side-channel mitigation, toggled via `StreamOptions.IncludeObfuscation`.
- Added `TestDeepCopyResponsesStreamResponsePreservesAllFields` to guard the deep-copy helper against silently dropping `Phase`, `SummaryIndex`, `Obfuscation`, `Status`, and `Signature`, and to assert pointer independence after copying.
- Added four new E2E harness cases covering: reasoning `summary_index` and `obfuscation` surviving a stream, `phase` appearing on assistant message items in a stream, and `phase` round-tripping correctly as an input field.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./framework/streaming/... -run TestDeepCopyResponsesStreamResponsePreservesAllFields -v
```

For E2E validation, run the updated provider harness collection against a live environment with a valid `openaiKey` and confirm:
- `summary_index` and `obfuscation` appear in the SSE body for the `o3-mini` streaming request.
- `phase` (`"final_answer"` or `"commentary"`) appears on assistant message items for the `gpt-5.3-codex` streaming request.
- The `phase` input round-trip request returns output items without an `unknown field "phase"` error.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes #3528

## Security considerations

The `Obfuscation` field is explicitly documented as random padding added to normalize delta event payload sizes as a side-channel mitigation. No new secrets, PII, or auth surfaces are introduced.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
@akshaydeo akshaydeo mentioned this pull request May 20, 2026
@etnperlong
etnperlong deleted the fix/minimal-fix-reasoning-summary-loss branch June 11, 2026 16:29
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.

3 participants