Skip to content

fix: complete Bedrock streaming Responses terminal events with the output array and reasoning payloads - #5668

Open
fus3r wants to merge 2 commits into
maximhq:devfrom
fus3r:fix-bedrock-responses-stream-terminal-events
Open

fus3r wants to merge 2 commits into
maximhq:devfrom
fus3r:fix-bedrock-responses-stream-terminal-events

Conversation

@fus3r

@fus3r fus3r commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Streaming /v1/responses on Bedrock never completed its terminal events: response.completed carried no output array ("output":null), reasoning done events were empty shells at all three lazy close sites (thinking deltas were never buffered, unlike text deltas since #3838), the thinking signature only existed as an in-flight delta, and citation annotations never reached the done events or the snapshot.
Structured-output streams were the worst case: the interception emitted bare text deltas with no item, no indices, no item id, so those streams produced no output_item events at all.
The non-streaming path returns all of this correctly. The streamed reasoning item was not even replayable: it had neither the content blocks the request converter reads nor a filled summary, so the next Converse request rebuilt zero reasoningContent blocks and silently lost the signed thinking chain.
Fourth and last member of the terminal-events series, after Anthropic (#5150), Gemini (#5260), and Cohere (#5386). Fixes #5667.

Changes

  • Track output items in BedrockResponsesStreamState: an OutputItems map fed by a small trackOutputItems helper wherever output_item.added/output_item.done are emitted, with a guard so an added shell never overwrites a completed item. The terminal snapshot's output is filled from it, sorted by output index, on both response.completed and response.incomplete, mirroring the Anthropic ingress.
  • Buffer thinking deltas (ReasoningTextBuffers) and the signature delta (ReasoningSignatures), and replace the three duplicated reasoning close sites with one closeReasoningItem helper that emits the done trio with the accumulated text and signature. The done item carries the payload as a reasoning content block next to the empty summary, the shape the non-streaming converter produces and the request converters replay, signature included. Reasoning text delta and done events now carry summary_index (0), which the OpenAI event types require.
  • Record citation annotations per output index, put them on the text item's done events instead of the hardcoded empty arrays, and fold late arrivals into the completed snapshot with clone-on-write so already-emitted events are never mutated. A matching fold covers a signature that arrives after its item closed.
  • Route the intercepted structured-output tool through a tracked synthetic text item: the interception opens it (output_item.added plus content_part.added), argument fragments are buffered and emitted as output_text.delta with output_index/content_index/item_id (previously bare), and the end-of-stream text close completes it. The tool_calls-to-stop stop-reason downgrade is preserved.
  • Reset the new state fields in both acquire and flush, same idiom as the existing ones, and add a changelog entry.

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

cd core
go test ./providers/bedrock/ -run 'TestBedrockResponsesStream' -v
go test ./providers/bedrock/

The ten new tests in streamterminalevents_test.go replay Converse event sequences through ToBifrostResponsesStream and FinalizeBedrockStream: the done trio at each of the three close sites, the completed output array and its payloads, annotations on the done events when they arrive before the close and in the snapshot either way, two concurrent reasoning blocks, the structured-output item, pooled-state recycling, and a full replay chain (the streamed reasoning and function-call items echoed back through ToBedrockResponsesRequest rebuild the Converse reasoningContent block with its text and signature before the toolUse block). Nine of them fail on dev without the fix with these exact symptoms; the structured-output test drives the interception helpers added here, since dev kept no state on that path and there was nothing to pin.

Screenshots/Recordings

Not a UI change.

Breaking changes

  • Yes
  • No

Terminal events that carried empty payloads now carry the accumulated ones, and the snapshot now includes the output array, matching the non-streaming path. The framework accumulator folds deltas and ignores done events, so logged messages are unchanged (verified). The Bedrock-format, invoke, and Anthropic-format egress serializers never read the output array from the completed event, so they are unaffected (verified).

Related issues

Closes #5667

Security considerations

None. No new inputs are parsed; the change only carries already-received stream content through to the terminal events.

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 Jul 29, 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6cd104c0-dcc1-489f-9726-e93b82552623

📥 Commits

Reviewing files that changed from the base of the PR and between 5f7103b and d465b76.

📒 Files selected for processing (4)
  • core/changelog.md
  • core/providers/bedrock/bedrock.go
  • core/providers/bedrock/responses.go
  • core/providers/bedrock/streamterminalevents_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • core/changelog.md
  • core/providers/bedrock/streamterminalevents_test.go
  • core/providers/bedrock/bedrock.go
  • core/providers/bedrock/responses.go

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved Bedrock streaming reliability for terminal events and completed response snapshots.
    • Preserved reasoning text, signatures, citations, and structured-output text throughout streaming.
    • Ensured output items remain complete, ordered, and available in final responses.
    • Fixed handling of streamed and late-arriving citations.
    • Improved structured-output terminal responses with non-empty final text and accurate completion status.
  • Tests

    • Added coverage for terminal events, concurrent reasoning blocks, citation propagation, structured output, replay, and stream-state isolation.

Walkthrough

Bedrock Responses streaming now buffers reasoning text and signatures, preserves citations, tracks output items for response.completed, supports replayable terminal reasoning events, and emits tracked synthetic text items for structured output.

Changes

Bedrock streaming terminal payloads

Layer / File(s) Summary
Stream state tracking and terminal item helpers
core/providers/bedrock/responses.go
The stream state buffers reasoning data, citations, output items, and structured-output text. Pooled state resets between streams.
Terminal event conversion and completion output
core/providers/bedrock/responses.go, core/providers/bedrock/streamterminalevents_test.go
Terminal events retain reasoning, signatures, and citations. response.completed.output contains ordered streamed items. Tests cover replay, interleaved reasoning, citations, structured output, and state recycling.
Structured-output synthetic text integration
core/providers/bedrock/bedrock.go, core/providers/bedrock/streamterminalevents_test.go, core/changelog.md
Structured-output fragments become tracked synthetic text events with chunk metadata and terminal snapshot coverage. The changelog records the streaming fixes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to d465b

This change completes Bedrock streaming terminal payloads and reasoning, citation, and structured-output events; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant BedrockStreamEvent
  participant BedrockResponsesStreamState
  participant FinalizeBedrockStream
  participant response.completed
  BedrockStreamEvent->>BedrockResponsesStreamState: buffer reasoning, citations, and output fragments
  BedrockResponsesStreamState->>FinalizeBedrockStream: provide cached items and late metadata
  FinalizeBedrockStream->>response.completed: emit ordered output items
Loading

Suggested reviewers: akshaydeo, tejasghatte, danpiths, sammaji

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.52% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 3 files. (1 skipped: 1 unsupported.) 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 clearly summarizes the primary fix for Bedrock streaming terminal events and reasoning payloads.
Description check ✅ Passed The description covers the required sections, explains the changes, and provides focused test commands and expected outcomes.
Linked Issues check ✅ Passed The changes address all coding objectives in issue #5667, including output arrays, reasoning payloads, signatures, citations, and structured output.
Out of Scope Changes check ✅ Passed The changelog update and regression tests directly support the Bedrock streaming fixes described in issue #5667.
✨ 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 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
core/providers/bedrock/streamterminalevents_test.go (1)

467-527: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Confirm the test's manual interception mirrors the handler's ordering.

This test reproduces the handler logic by hand (beginStructuredOutputTextItem then appendStructuredOutputText), so it can drift from BedrockProvider.ResponsesStream in core/providers/bedrock/bedrock.go (Lines 1885-1934) without failing. In particular the handler also sets isAccumulatingStructuredOutput and gates the delta branch on it, and it suppresses non-tool text/reasoning deltas — neither is exercised here. Consider adding a case that feeds the suppression path (a text delta between tool-arg deltas) so a regression in the handler's gating is caught.

🤖 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/bedrock/streamterminalevents_test.go` around lines 467 - 527,
Extend TestBedrockResponsesStreamStructuredOutputSnapshot to exercise
BedrockProvider.ResponsesStream’s structured-output gating, including setting
isAccumulatingStructuredOutput and inserting a non-tool text or reasoning delta
between tool-argument deltas. Assert that the suppressed delta produces no
output while the tool fragments remain ordered and the final structured-output
snapshot is unchanged.
core/providers/bedrock/bedrock.go (1)

1894-1907: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the chunk-emit loop; it is now duplicated three times.

Lines 1894-1907, 1921-1934, and the generic path at Lines 1957-1972 are the same body: stamp ExtraFields, bump chunkIndex/lastChunkTime, optionally attach the raw payload, send. The two new copies also drop the response != nil guard the generic loop has. A small closure over ctx/chunkIndex/lastChunkTime/message.Payload keeps them from drifting.

♻️ Suggested closure
// Declare once, after lastChunkTime is initialized:
emit := func(responses []*schemas.BifrostResponsesStreamResponse, rawPayload []byte) {
	for _, response := range responses {
		if response == nil {
			continue
		}
		response.ExtraFields = schemas.BifrostResponseExtraFields{
			ChunkIndex: chunkIndex,
			Latency:    time.Since(lastChunkTime).Milliseconds(),
		}
		chunkIndex++
		lastChunkTime = time.Now()

		if providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse) {
			response.ExtraFields.RawResponse = string(rawPayload)
		}

		providerUtils.ProcessAndSendResponse(ctx, postHookRunner, providerUtils.GetBifrostResponseForStreamResponse(nil, nil, response, nil, nil, nil), responseChan, postHookSpanFinalizer)
	}
}

Then both new blocks collapse to:

-						for _, response := range streamState.appendStructuredOutputText(streamEvent.Delta.ToolUse.Input, contentBlockIndex, chunkIndex) {
-							response.ExtraFields = schemas.BifrostResponseExtraFields{
-								ChunkIndex: chunkIndex,
-								Latency:    time.Since(lastChunkTime).Milliseconds(),
-							}
-							chunkIndex++
-							lastChunkTime = time.Now()
-
-							if providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse) {
-								response.ExtraFields.RawResponse = string(message.Payload)
-							}
-
-							providerUtils.ProcessAndSendResponse(ctx, postHookRunner, providerUtils.GetBifrostResponseForStreamResponse(nil, nil, response, nil, nil, nil), responseChan, postHookSpanFinalizer)
-						}
+						emit(streamState.appendStructuredOutputText(streamEvent.Delta.ToolUse.Input, contentBlockIndex, chunkIndex), message.Payload)

Also applies to: 1921-1934

🤖 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/bedrock/bedrock.go` around lines 1894 - 1907, Extract the
duplicated response-processing loop into a single closure near the
initialization of lastChunkTime, using captured ctx, chunkIndex, lastChunkTime,
and the existing send dependencies. Have the closure skip nil responses, stamp
ExtraFields, update chunk timing, attach the optional raw payload, and send each
response; replace the loops at the structured-output paths and generic path with
calls to this closure.
🤖 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.

Inline comments:
In `@core/providers/bedrock/responses.go`:
- Around line 1237-1239: Keep the assignment in the reasoningContentDelta
handling path as a direct replacement of state.ReasoningSignatures[outputIndex]
with the single reasoningDelta.Signature value. Do not append or accumulate
signature data, since each source delta carries only one signature payload;
update the surrounding comment only if the source contract changes.

---

Nitpick comments:
In `@core/providers/bedrock/bedrock.go`:
- Around line 1894-1907: Extract the duplicated response-processing loop into a
single closure near the initialization of lastChunkTime, using captured ctx,
chunkIndex, lastChunkTime, and the existing send dependencies. Have the closure
skip nil responses, stamp ExtraFields, update chunk timing, attach the optional
raw payload, and send each response; replace the loops at the structured-output
paths and generic path with calls to this closure.

In `@core/providers/bedrock/streamterminalevents_test.go`:
- Around line 467-527: Extend TestBedrockResponsesStreamStructuredOutputSnapshot
to exercise BedrockProvider.ResponsesStream’s structured-output gating,
including setting isAccumulatingStructuredOutput and inserting a non-tool text
or reasoning delta between tool-argument deltas. Assert that the suppressed
delta produces no output while the tool fragments remain ordered and the final
structured-output snapshot is unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b45241da-c5eb-4c53-a248-942a09558093

📥 Commits

Reviewing files that changed from the base of the PR and between ec1dd92 and a62fcfd.

📒 Files selected for processing (4)
  • core/changelog.md
  • core/providers/bedrock/bedrock.go
  • core/providers/bedrock/responses.go
  • core/providers/bedrock/streamterminalevents_test.go

Comment thread core/providers/bedrock/responses.go
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 6, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 7, 2026
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review August 10, 2026 22:20

The merge-base changed after approval.

@akshaydeo
akshaydeo requested a review from a team as a code owner August 10, 2026 22:20
@fus3r
fus3r force-pushed the fix-bedrock-responses-stream-terminal-events branch from cda61a5 to 7691011 Compare August 11, 2026 11:14
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 16, 2026
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review August 19, 2026 08:15

The merge-base changed after approval.

@CLAassistant

CLAassistant commented Aug 20, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@fus3r
fus3r force-pushed the fix-bedrock-responses-stream-terminal-events branch from 828d787 to d465b76 Compare August 22, 2026 20:08
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Warning

Your free Security trial is over. An organization admin can activate billing to continue.

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants