Skip to content

fix: responses stream events - #3838

Merged
akshaydeo merged 1 commit into
devfrom
05-28-fix_responses_stream_events
May 29, 2026
Merged

fix: responses stream events#3838
akshaydeo merged 1 commit into
devfrom
05-28-fix_responses_stream_events

Conversation

@TejasGhatte

@TejasGhatte TejasGhatte commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

output_text.done, content_part.done, and output_item.done stream events were being emitted with empty text content instead of the full accumulated text. This PR fixes that by introducing a TextBuffers map in each provider's stream state to accumulate text deltas as they arrive, then populating the done events with the complete text.

Additionally, tools with a nil or empty name are now skipped before being sent to Anthropic, which previously caused Anthropic to reject the request.

Changes

  • Added TextBuffers map[int]string to the stream state structs for Anthropic, Bedrock, and Cohere providers, accumulating text deltas keyed by output index
  • Updated output_text.done, content_part.done, and output_item.done events across all four providers (Anthropic, Bedrock, Cohere, Gemini) to include the full accumulated text in their payloads rather than empty strings
  • Populated ContentBlocks in output_item.done messages with the actual text content block instead of an empty slice
  • Cleaned up TextBuffers entries via delete after emitting done events to avoid stale state
  • Ensured TextBuffers is properly initialized and cleared in pool acquire/flush paths
  • Skipped Anthropic tool conversion when tool.Name is nil or empty to prevent Anthropic API rejections

Type of change

  • Bug fix

Affected areas

  • Core (Go)
  • Providers/Integrations

How to test

go test ./...

Stream a response from each affected provider (Anthropic, Bedrock, Cohere, Gemini) using the Responses API and verify that:

  • output_text.done events contain the full assembled text
  • content_part.done events include a Part with the full text
  • output_item.done events include a Content.ContentBlocks array with the complete text block
  • Sending a tool with no name to Anthropic no longer causes a request rejection

Breaking changes

  • Yes
  • No

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

Summary by CodeRabbit

  • Bug Fixes
    • Streaming responses now accumulate per-output text and include full text in final completion events (avoids empty text/content blocks) across provider integrations.
    • Tool entries with missing or empty names are skipped during processing.
    • Stream state pooling lifecycle fixed to prevent cross-request text reuse by allocating, clearing, and releasing per-output text buffers between requests.

Review Change Stack

@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.


tejas ghatte seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds per-output TextBuffers across Anthropic, Bedrock, Cohere, and Gemini stream converters to accumulate text deltas and emit the complete buffered text in final "done" events; Anthropic also skips tools with empty names.

Changes

Text buffering for accumulated text in done events

Layer / File(s) Summary
Anthropic text accumulation and done event emission
core/providers/anthropic/responses.go
AnthropicResponsesStreamState gains a TextBuffers map initialized and cleared through the pooling lifecycle. Text deltas are accumulated during streaming, and on ContentBlockStop for text the accumulated text populates output_text.done, content_part.done, and output_item.done. Tools with nil or empty names are skipped during conversion.
Bedrock text accumulation and done event emission
core/providers/bedrock/responses.go
BedrockResponsesStreamState adds a TextBuffers map in the pool and acquisition flow. Text deltas are appended to buffers; when closing text outputs (before tool calls) or finalizing streams, converters emit output_text.done/content_part.done and conditionally include text ContentBlocks in output_item.done only when buffered text is non-empty, then delete buffers.
Cohere text accumulation for regular and tool-plan text
core/providers/cohere/responses.go
CohereResponsesStreamState adds TextBuffers. Regular and tool-plan deltas are buffered; on content end or tool-call transitions the buffered text populates output_text.done, content_part.done, and output_item.done (text blocks added only when non-empty), and buffers are cleared after use.
Gemini text output in done events
core/providers/gemini/responses.go
closeGeminiTextItem now uses the buffered fullText to set content_part.done text and populates output_item.done.content_blocks with a single text block containing the accumulated text instead of emitting empty payloads.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • maximhq/bifrost#3527: Modifies FinalizeBedrockStream; related to Bedrock finalization and output synthesis.

Suggested reviewers

  • danpiths
  • akshaydeo

"🐰 I buffered words through streaming light,
Chunks stitched gently into done-event sight,
No more empty shells where content should be,
Outputs now carry the full text you see,
Hooray for buffers—hop, munch, and delight!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix: responses stream events' is vague and generic, referring to 'responses' and 'stream events' without specifying which aspect of the stream events is being fixed. Consider a more specific title such as 'fix: populate stream done events with accumulated text' or 'fix: include full text in output_text.done and related events'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive with clear sections covering summary, changes, type, affected areas, testing instructions, and checklist items that mostly align with the template.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 05-28-fix_responses_stream_events

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"


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

TejasGhatte commented May 28, 2026

Copy link
Copy Markdown
Collaborator Author

@TejasGhatte
TejasGhatte marked this pull request as ready for review May 28, 2026 11:45
@greptile-apps

greptile-apps Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

Safe to merge; the changes are targeted streaming-state fixes with no broad interface changes and correct pool lifecycle handling across all four providers.

The text-buffer accumulation pattern is applied consistently, pool acquire and flush paths both reset the new map field, the delete-after-done approach avoids stale state within a stream, and the nil-tool guard in convertBifrostToolToAnthropic is handled correctly by its caller. No new concurrency concerns are introduced.

core/providers/cohere/responses.go — the two ToolPlan output_item.done paths unconditionally emit a content block even when accumulated text is empty (noted in a prior review thread).

Important Files Changed

Filename Overview
core/providers/anthropic/responses.go Adds TextBuffers map[int]*strings.Builder to stream state for text accumulation; properly initializes in pool acquire, nils in flush; correctly populates output_text.done, content_part.done, and output_item.done with accumulated text; nil return from convertBifrostToolToAnthropic is checked by its sole caller at line 5035
core/providers/bedrock/responses.go TextBuffers added and integrated into both text-delta accumulation paths (new-block and regular-delta); done events in ToBifrostResponsesStream and FinalizeBedrockStream both read and delete from TextBuffers correctly; flush clears instead of nils (minor inconsistency with Anthropic) but functionally sound
core/providers/cohere/responses.go TextBuffers integrated into text and tool-plan delta paths; non-ToolPlan non-reasoning output_item.done correctly guards with if accText != ""; ToolPlan output_item.done unconditionally emits a content block even when accText is empty (flagged in prior review thread)
core/providers/gemini/responses.go closeGeminiTextItem extended to populate content_part.done and output_item.done using the already-accumulated fullText from state.TextBuffer; no new buffer management needed since Gemini already had a single TextBuffer field; change is straightforward and correct

Reviews (6): Last reviewed commit: "fix: responses stream events" | Re-trigger Greptile

Comment thread core/providers/cohere/responses.go
Comment thread core/providers/anthropic/responses.go
@TejasGhatte
TejasGhatte force-pushed the 05-28-fix_responses_stream_events branch 2 times, most recently from 8688282 to 8bf07c3 Compare May 29, 2026 09:16

@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: 3

🤖 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/anthropic/responses.go`:
- Around line 5239-5242: The guard that skips tools currently checks only for
nil or empty string (tool.Name == nil || *tool.Name == "") but should also treat
whitespace-only names as missing; update the check in the skip-tools block to
use strings.TrimSpace on *tool.Name (e.g., if tool.Name == nil ||
strings.TrimSpace(*tool.Name) == "" { return nil }) and add the strings import
if not already present so whitespace-only names are rejected before sending tool
definitions upstream.
- Around line 1345-1349: The code is unconditionally persisting fallback
doneItem into state.OutputItems which can pollute response.completed; wrap the
cloning/storage (the block that clones doneItem, clones doneItem.Content, sets
cloned.Content and assigns state.OutputItems[outputIndex] = &cloned) with a
guard that ensures the synthesized fallback is a text-only item (e.g., check
doneItem.Content is non-nil and that the content type/mime or text field
indicates plain text — use the project's canonical field such as Content.Type or
Content.Mime or presence of Content.Text). Only when that text-only check passes
perform the clone/store; otherwise skip modifying state.OutputItems so non-text
reasoning/MCP paths do not get persisted.

In `@core/providers/bedrock/responses.go`:
- Around line 816-818: The TextBuffers accumulation currently does repeated
string concatenation (state.TextBuffers[outputIndex] += text) causing quadratic
copies; change TextBuffers from map[int]string (or []string) to
map[int]*strings.Builder (or []*strings.Builder), initialize per-output builders
where outputs are created, replace every place that appends (e.g., where
state.TextBuffers[outputIndex] += text and similar at line ~845) with
builder.WriteString(text), and when emitting the done event (where accText :=
state.TextBuffers[outputIndex] around ~1289) call accText := builder.String()
(and delete the builder entry if needed). Update references to state.ItemIDs and
outputIndex accordingly so you locate the correct builder for each output.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: c422b722-aee0-48e8-a3b7-34abd22a6f29

📥 Commits

Reviewing files that changed from the base of the PR and between d5e2ea4 and 8bf07c3.

📒 Files selected for processing (4)
  • core/providers/anthropic/responses.go
  • core/providers/bedrock/responses.go
  • core/providers/cohere/responses.go
  • core/providers/gemini/responses.go

Comment thread core/providers/anthropic/responses.go Outdated
Comment thread core/providers/anthropic/responses.go
Comment thread core/providers/bedrock/responses.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/bedrock/responses.go (1)

392-460: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add table-driven coverage for both text close paths.

This behavior now exists in two separate branches: closing text before a tool block starts, and closing text during FinalizeBedrockStream. Please add focused table-driven tests for both so regressions don't bring back empty output_text.done / output_item.done payloads or miss buffer cleanup.

As per coding guidelines, "Apply standard Go review practices: small interfaces, clear error wrapping, context propagation, race-safe shared state, goroutine/channel cleanup, and table-driven tests for behavior changes."

Also applies to: 1289-1357

🤖 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/responses.go` around lines 392 - 460, Add table-driven
tests that exercise both code paths that close buffered text: (1) the branch
that emits output_text.done/output_item.done when closing text before a tool
block starts and (2) the branch exercised by FinalizeBedrockStream. For each
table case, set up state.TextBuffers with a variety of
prevOutputIndex/prevAccText values (including empty and non-empty), call the
code-path (the handler that emits output_text.done / content_part.done /
output_item.done and FinalizeBedrockStream), and assert that responses contain
non-empty Text and ContentBlocks when prevAccText is non-empty, that no empty
output_text.done or output_item.done payloads are emitted, and that the
corresponding entry is removed from state.TextBuffers after the call;
parameterize cases for empty vs non-empty buffers and for invoking the close
logic directly vs via FinalizeBedrockStream to prevent regressions.
🤖 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/bedrock/responses.go`:
- Around line 392-460: Add table-driven tests that exercise both code paths that
close buffered text: (1) the branch that emits output_text.done/output_item.done
when closing text before a tool block starts and (2) the branch exercised by
FinalizeBedrockStream. For each table case, set up state.TextBuffers with a
variety of prevOutputIndex/prevAccText values (including empty and non-empty),
call the code-path (the handler that emits output_text.done / content_part.done
/ output_item.done and FinalizeBedrockStream), and assert that responses contain
non-empty Text and ContentBlocks when prevAccText is non-empty, that no empty
output_text.done or output_item.done payloads are emitted, and that the
corresponding entry is removed from state.TextBuffers after the call;
parameterize cases for empty vs non-empty buffers and for invoking the close
logic directly vs via FinalizeBedrockStream to prevent regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1834ff66-08e4-426a-9a9f-3090434e9b00

📥 Commits

Reviewing files that changed from the base of the PR and between 8bf07c3 and ea20dcd.

📒 Files selected for processing (4)
  • core/providers/anthropic/responses.go
  • core/providers/bedrock/responses.go
  • core/providers/cohere/responses.go
  • core/providers/gemini/responses.go

@TejasGhatte
TejasGhatte force-pushed the 05-28-fix_responses_stream_events branch from ea20dcd to 0a83eca Compare May 29, 2026 11:37

@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

♻️ Duplicate comments (1)
core/providers/anthropic/responses.go (1)

5249-5255: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject whitespace-only tool names too.

" " still passes this guard and gets forwarded as a custom tool name, which Anthropic will reject the same way it rejects an empty name. Trim before the emptiness check.

💡 Suggested fix
-	if tool.Name == nil || *tool.Name == "" {
+	if tool.Name == nil || strings.TrimSpace(*tool.Name) == "" {
 		return nil
 	}
As per coding guidelines, validate untrusted input before provider calls.
🤖 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/anthropic/responses.go` around lines 5249 - 5255, The guard
that skips tools with empty names only checks for nil or exact empty string, but
does not reject whitespace-only names; update the validation in the code that
inspects tool.Name (before constructing AnthropiсTool) to trim the string and
treat it as empty if all whitespace (e.g., call strings.TrimSpace on *tool.Name)
so that whitespace-only names are rejected the same way as "" — ensure this
change is applied where tool.Name is read and before creating/setting
anthropicTool.Name.
🤖 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/cohere/responses.go`:
- Around line 340-341: When closing a synthetic tool-plan item you clear
state.ToolPlanOutputIndex but leave state.ContentIndexToOutputIndex[0] pointing
to that output index; update both tool-plan close sites (the blocks that delete
from state.TextBuffers and set state.ToolPlanOutputIndex = nil) to also delete
state.ContentIndexToOutputIndex[0] so the mapping is removed, preventing
getOrCreateOutputIndex() from reusing a closed output index; apply the same
deletion in both locations mentioned and ensure any code relying on
ContentIndexToOutputIndex checks for existence after this cleanup.

---

Duplicate comments:
In `@core/providers/anthropic/responses.go`:
- Around line 5249-5255: The guard that skips tools with empty names only checks
for nil or exact empty string, but does not reject whitespace-only names; update
the validation in the code that inspects tool.Name (before constructing
AnthropiсTool) to trim the string and treat it as empty if all whitespace (e.g.,
call strings.TrimSpace on *tool.Name) so that whitespace-only names are rejected
the same way as "" — ensure this change is applied where tool.Name is read and
before creating/setting anthropicTool.Name.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 280b84b6-9f5b-435a-88d3-f92e52fc45a9

📥 Commits

Reviewing files that changed from the base of the PR and between ea20dcd and 0a83eca.

📒 Files selected for processing (4)
  • core/providers/anthropic/responses.go
  • core/providers/bedrock/responses.go
  • core/providers/cohere/responses.go
  • core/providers/gemini/responses.go

@TejasGhatte
TejasGhatte force-pushed the 05-28-fix_responses_stream_events branch from 0a83eca to 9a60961 Compare May 29, 2026 11:57

@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

🤖 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/cohere/responses.go`:
- Around line 340-344: When closing the synthetic tool-plan item, don’t just
delete state.ContentIndexToOutputIndex[0] and state.TextBuffers[outputIndex];
you must reserve/advance the global output counter so the next
getOrCreateOutputIndex() cannot re-use the same output index. Update the
tear-down in the block that currently sets state.ToolPlanOutputIndex = nil to
also advance state.CurrentOutputIndex past the released outputIndex (or set
CurrentOutputIndex = max(CurrentOutputIndex, outputIndex+1)) and then clear
state.ToolPlanOutputIndex, ensuring getOrCreateOutputIndex() will allocate a new
unique index for subsequent real content/tool items.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 7ad85204-be16-46b3-b52d-118162ccdd38

📥 Commits

Reviewing files that changed from the base of the PR and between 0a83eca and 9a60961.

📒 Files selected for processing (4)
  • core/providers/anthropic/responses.go
  • core/providers/bedrock/responses.go
  • core/providers/cohere/responses.go
  • core/providers/gemini/responses.go

Comment thread core/providers/cohere/responses.go

akshaydeo commented May 29, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • May 29, 12:30 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 29, 12:31 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo merged commit 2201089 into dev May 29, 2026
16 of 17 checks passed
@akshaydeo
akshaydeo deleted the 05-28-fix_responses_stream_events branch May 29, 2026 12:31
akshaydeo pushed a commit that referenced this pull request May 29, 2026
## Summary

`output_text.done`, `content_part.done`, and `output_item.done` stream events were being emitted with empty text content instead of the full accumulated text. This PR fixes that by introducing a `TextBuffers` map in each provider's stream state to accumulate text deltas as they arrive, then populating the done events with the complete text.

Additionally, tools with a `nil` or empty name are now skipped before being sent to Anthropic, which previously caused Anthropic to reject the request.

## Changes

- Added `TextBuffers map[int]string` to the stream state structs for Anthropic, Bedrock, and Cohere providers, accumulating text deltas keyed by output index
- Updated `output_text.done`, `content_part.done`, and `output_item.done` events across all four providers (Anthropic, Bedrock, Cohere, Gemini) to include the full accumulated text in their payloads rather than empty strings
- Populated `ContentBlocks` in `output_item.done` messages with the actual text content block instead of an empty slice
- Cleaned up `TextBuffers` entries via `delete` after emitting done events to avoid stale state
- Ensured `TextBuffers` is properly initialized and cleared in pool acquire/flush paths
- Skipped Anthropic tool conversion when `tool.Name` is `nil` or empty to prevent Anthropic API rejections

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

```sh
go test ./...
```

Stream a response from each affected provider (Anthropic, Bedrock, Cohere, Gemini) using the Responses API and verify that:
- `output_text.done` events contain the full assembled text
- `content_part.done` events include a `Part` with the full text
- `output_item.done` events include a `Content.ContentBlocks` array with the complete text block
- Sending a tool with no name to Anthropic no longer causes a request rejection

## Breaking changes

- [ ] Yes
- [x] No

## 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

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Bug Fixes**
  * Streaming responses now accumulate per-output text and include full text in final completion events (avoids empty text/content blocks) across provider integrations.
  * Tool entries with missing or empty names are skipped during processing.
  * Stream state pooling lifecycle fixed to prevent cross-request text reuse by allocating, clearing, and releasing per-output text buffers between requests.

<!-- review_stack_entry_start -->

[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/maximhq/bifrost/pull/3838?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@akshaydeo akshaydeo mentioned this pull request May 29, 2026
18 tasks
akshaydeo added a commit that referenced this pull request May 29, 2026
## Summary

This PR releases **core v1.5.14**, **framework v1.3.14**, **transports v1.5.6**, and bumps all dependent plugins to their respective `.14` patch versions. It delivers a broad set of new capabilities across MCP authentication, key rotation, OTel metrics, Bedrock/Anthropic compatibility, and UI improvements, alongside a number of targeted bug fixes and refactors.

## Changes

- **Direct API Key Header** — Providers can now receive an API key passed directly via a request header (#3817)
- **MCP Per-User Auth** — Introduced `MCPCredentialStore` abstraction, per-user MCP credential reconciliation, and a new per-user header auth type with lazy-auth submission flow (#3656, #3702, #3703, #3704, #3705)
- **MCP TLS Configuration** — Added configurable TLS (`insecureSkipVerify`, `caCertPem`) for HTTP/SSE MCP client connections (#3779, #3783)
- **MCP Sessions Management** — Filter, search, and pagination on the MCP sessions list API and table, plus a `can_reauth` identity gate (#3823, #3824, #3825)
- **Key Rotation** — Keys now rotate on 401/402/403 responses; returns `502 upstream_credentials_exhausted` when all keys are permanently exhausted. Added `triggered_rotation` to `KeyAttemptRecord` and tightened `bifrost_key_rotation_events_total` semantics (#3430, #3491)
- **OTel Metrics** — Added OTel spec-compatible metrics (backward compatible) with provider cache and semantic cache attributes in metrics export (#3865, #3816)
- **Opus 4.8 Support** — System message handling and general compatibility for Opus 4.8 (#3868, #3878)
- **Dimension Rankings** — New `GetDimensionRankings` API and dashboard tabs for team, customer, BU, and user rankings (#3766)
- **Model Pricing Attributes** — `additional_attributes` field on model pricing rows with management API and UI editor (#3829)
- **Prompt Cache Retention** — Added prompt cache retention parameter on responses requests (#3810)
- **Tool Call Execution UI** — Inline tool-call execution, stop streaming, bulk execute/submit, and a redesigned tool-call UI (#3837, #3843)
- **Sheet Navigation** — Prev/next keyboard navigation and URL state across virtual key, MCP client, and routing rule sheets (#3739, #3740, #3744, #3745)
- **Bedrock Tool Name Truncation** — Truncate Bedrock function/tool names to the provider length limit
- **Bedrock Guardrails** — Set guardrail config in Bedrock requests built from responses (#3862)
- **Anthropic Tool Use** — Default `tool_use` input to `{}` when arguments are absent (#3880)
- **Responses Streaming** — Fixed responses stream events (#3838)
- **Compat Flow** — Fixed missing parameter parsing on the compat flow (#3881)
- **Passthrough API Version** — Set a default API version in passthrough requests as a fallback (#3853)
- **Virtual Key Updates** — Avoid overriding optional fields during virtual key update (#3855)
- **User-Mode Flows** — Gate user-mode flows on caller `user_id`, skip temp token mint, and unify flow/credential kind filtering for pending flows (#3841, #3859)
- **Partial Tool Calls** — Handle partial tool call execution failures and return successful results (#3849)
- **URL Query Escaping** — Support escaped characters in URL query parameters (#3826)
- **MCP Auth Errors** — Inline banner and retry support for MCP auth-required errors (#3856)
- **Renamed Resolvers** — `staticHeadersResolver`/`serverOAuthResolver` renamed to `sharedHeadersResolver`/`sharedOAuthResolver` (#3840)
- **Starlark Nested Tool Calls** — Exposed `RunWithPluginPipeline` on `ClientManager` and routed Starlark nested tool calls through the canonical plugin gate (#3794)
- **Deferred-Fill OAuth Removed** — Removed deferred-fill user-mode OAuth flow support (#3839)
- **Go 1.26.3** — Upgraded toolchain to Go 1.26.3 (#3782)

## Type of change

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

## Affected areas

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

## How to test

```sh
# Core/Transports
go version  # should report go1.26.3
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

- Validate MCP per-user auth by configuring a per-user header auth type and confirming credentials are stored and reconciled on virtual key and MCP client changes.
- Validate key rotation by triggering a 401/402/403 from an upstream provider and confirming rotation occurs; exhaust all keys and confirm a `502 upstream_credentials_exhausted` is returned.
- Validate OTel metrics output includes `provider_cache` and `semantic_cache` attributes.
- Validate Bedrock requests with tool names exceeding the provider limit are truncated correctly.
- Validate Opus 4.8 system message handling by sending a request with a system message to an Opus 4.8 endpoint.

## Breaking changes

- [x] Yes
- [ ] No

The deferred-fill user-mode OAuth flow has been removed (#3839). Any integrations relying on that flow must migrate to the new per-user credential store approach. The `staticHeadersResolver` and `serverOAuthResolver` identifiers have been renamed to `sharedHeadersResolver` and `sharedOAuthResolver` respectively (#3840); any direct references must be updated.

## Related issues

#3817, #3656, #3702, #3703, #3704, #3705, #3779, #3783, #3823, #3824, #3825, #3430, #3491, #3865, #3816, #3868, #3878, #3766, #3829, #3810, #3837, #3843, #3739, #3740, #3744, #3745, #3862, #3880, #3838, #3881, #3853, #3855, #3841, #3859, #3849, #3826, #3856, #3840, #3794, #3839, #3782, #3724, #3814, #3836, #3869, #3886

## Security considerations

- MCP per-user credentials are stored via the new `MCPCredentialStore` abstraction; ensure the backing store is appropriately access-controlled and that credential values are encrypted at rest.
- The direct API key header feature passes provider secrets via HTTP headers; ensure TLS is enforced on all ingress paths and that headers are not logged in plaintext.
- User-mode flows are now gated on `caller user_id` and temp token minting is skipped where appropriate, reducing the surface for privilege escalation.
- TLS configuration for MCP HTTP/SSE connections supports `insecureSkipVerify`; this should only be enabled in controlled environments.

## 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)
- [x] I verified the CI pipeline passes locally if applicable
@akshaydeo akshaydeo mentioned this pull request May 29, 2026
akshaydeo added a commit that referenced this pull request May 29, 2026
## ✨ Features

- **Direct API Key Header** - Pass a provider API key directly via
request header (#3817)
- **MCP Per-User Authentication** - New per-user header auth type with
credential storage
  and lazy-auth submission flow (#3703, #3704, #3705)
- **MCP TLS Configuration** - Configurable TLS (insecureSkipVerify,
caCertPem) for HTTP/SSE
  MCP client connections (#3779, #3783)
- **MCP Sessions Management** - Filter, search, and pagination on the
MCP sessions list API
  and table, plus a can_reauth identity gate (#3823, #3824, #3825)
- **Tool Call Execution UI** - Inline tool-call execution, stop
streaming, bulk
  execute/submit, and a redesigned tool-call UI (#3837, #3843)
- **Dimension Rankings Dashboard** - New dashboard tabs for team,
customer, BU, and user
  rankings, backed by a GetDimensionRankings API (#3766)
- **Model Pricing Attributes** - additional_attributes on model pricing
rows with management
  API and UI editor (#3829)
- **Prompt Cache Retention** - Prompt cache retention parameter on
responses requests
  (#3810)
- **Opus 4.8 Support** - System message handling and compatibility for
Opus 4.8 (#3878,
  #3868)
  - **Key Rotation** - Rotate keys on 401/402/403 and return 502
upstream_credentials_exhausted when all keys are permanently dead
(#3491)
- **OTel Metrics** - OTel spec compatible metrics plus provider and
semantic cache
  attributes in metrics export (#3865, #3816)
- **Sheet Navigation** - Prev/next keyboard navigation and URL state
across virtual key, MCP
  client, and routing rule sheets (#3739, #3740, #3744, #3745)
  - **Go 1.26.3** - Upgraded toolchain to Go 1.26.3 (#3782)

  ## 🐞 Fixed

- **Bedrock Tool Names** - Truncate Bedrock function/tool names to the
provider length limit
- **Bedrock Guardrails** - Set guardrail config in Bedrock request built
from responses
  (#3862)
- **Anthropic Tool Use** - Default Anthropic tool_use input to {} when
arguments are absent
  (#3880)
  - **Responses Streaming** - Fixed responses stream events (#3838)
- **Compat Flow** - Fixed missing parameter parsing on the compat flow
(#3881)
- **Passthrough API Version** - Set a default API version in passthrough
requests as a
  fallback (#3853)
- **Virtual Key Updates** - Avoid overriding optional fields during
virtual key update
  (#3855)
- **User-Mode Flows** - Gate user-mode flows on caller user_id, skip
temp token mint, and
  unify flow/credential kind filtering for pending flows (#3841, #3859)
- **Partial Tool Calls** - Handle partial tool call execution failures
and return successful
  results (#3849)
- **URL Query Escaping** - Support escaped characters in URL query
parameters (#3826)
- **MCP Auth Errors** - Inline banner and retry support for MCP
auth-required errors (#3856)
- **JSON Editor Height** - Cap JSON editor max height at 400px in
message views (#3842)
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