Skip to content

feat(ui): extract shared FilterSidebarTrigger component with mobile topbar portal - #6232

Merged
akshaydeo merged 1 commit into
devfrom
08-17-fix_responsive_view_fixes_post_notification_center_addition
Aug 17, 2026
Merged

feat(ui): extract shared FilterSidebarTrigger component with mobile topbar portal#6232
akshaydeo merged 1 commit into
devfrom
08-17-fix_responsive_view_fixes_post_notification_center_addition

Conversation

@impoiler

@impoiler impoiler commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Extracts the duplicated collapsed-state filter sidebar trigger button into a shared FilterSidebarTrigger component and improves the mobile topbar layout so the filter trigger appears inline with the notification bell rather than as a floating overlay.

Changes

  • Added ui/components/filters/filterSidebarTrigger.tsx — a new shared component that renders the collapsed filter sidebar trigger. On desktop it renders the existing full-height sidebar rail button. On mobile it portals a compact icon button into a new mobileFilterSlot anchor in the topbar, placing it immediately before the notification bell.
  • Replaced the duplicated inline <Button> collapsed-state blocks in logsFilterSidebar, mcpFilterSidebar, mcpLibraryFilterSidebar, mcpClientsFilterSidebar, mcpSessionsFilterSidebar, and oauthGrantsFilterSidebar with a single <FilterSidebarTrigger /> call.
  • Added mobileFilterSlot and setMobileFilterSlot to TopbarContext and exposed useMobileFilterSlot / useMobileFilterSlotRef hooks so filter sidebars can portal their mobile trigger into the topbar without the topbar needing to know page-specific content.
  • Updated Topbar to render the mobileFilterSlot anchor span between the left content area and the notification bell, and to show the brand logo on mobile in place of the page title (which is now hidden on small screens).
  • Collapsed the user pill on mobile to a bare icon button, hiding the display name and chevron below the md breakpoint.
  • Changed the notification badge to use explicit bg-red-600/dark:bg-red-700 classes instead of bg-destructive to ensure consistent color regardless of theme token overrides.

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 ui
pnpm i
pnpm build
  1. Open any page that has a filter sidebar (Logs, MCP Logs, MCP Library, MCP Clients, MCP Sessions, OAuth Grants).
  2. Collapse the filter sidebar and verify the trigger appears correctly on desktop (full-height rail) and mobile (icon in topbar next to the notification bell).
  3. Confirm the active filter count badge renders on both breakpoints when filters are applied.
  4. Verify the mobile topbar shows the brand logo and a bare user icon, with the full pill restored at the md breakpoint.
  5. Confirm the notification badge is visually red in both light and dark themes.

Screenshots/Recordings

Before/after screenshots recommended for the mobile topbar layout and the collapsed filter trigger placement on both breakpoints.

Breaking changes

  • Yes
  • No

Related issues

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 Aug 17, 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: dcf08755-18d3-4f13-94d9-ddfb3041d12c

📥 Commits

Reviewing files that changed from the base of the PR and between 0416be5 and e597603.

📒 Files selected for processing (1)
  • ui/components/notificationCenter.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • ui/components/notificationCenter.tsx

Limit details: You’ve used all 3 included reviews currently available under your plan. You completed 70 included PR reviews in the past 7 days; at that activity level, included reviews refill at 3 reviews per hour.


📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Standardized filter sidebar controls across library, client, session, OAuth grant, logs, and MCP views.
    • Added responsive mobile filter controls in the top bar.
    • Improved mobile top-bar layout with branding and a compact account control.
  • Style

    • Improved notification unread badge contrast with a red background and white text.

Walkthrough

The change adds a shared responsive filter trigger, connects it to the topbar mobile slot, and replaces custom collapsed-sidebar controls across six filter sidebars. The topbar also gains responsive branding and account controls. Notification badge colors now use explicit red and white classes.

Changes

Responsive filter trigger

Layer / File(s) Summary
Topbar mobile filter slot
ui/lib/contexts/topbarContext.tsx, ui/components/topbar.tsx
The topbar context stores a mobile filter slot. The topbar renders that slot with mobile branding and responsive account controls.
Shared responsive trigger
ui/components/filters/filterSidebarTrigger.tsx
FilterSidebarTrigger renders mobile and desktop controls, invokes onClick, displays positive active-filter counts, and supports test IDs.
Filter sidebar adoption
ui/app/workspace/mcp-registry/library/views/mcpLibraryFilterSidebar.tsx, ui/app/workspace/mcp-registry/views/mcpClientsFilterSidebar.tsx, ui/app/workspace/mcp-sessions/views/mcpSessionsFilterSidebar.tsx, ui/app/workspace/oauth-grants/views/oauthGrantsFilterSidebar.tsx, ui/components/filters/logsFilterSidebar.tsx, ui/components/filters/mcpFilterSidebar.tsx
The filter sidebars replace local collapsed-state buttons with FilterSidebarTrigger and retain their toggle handlers, active counts, and existing test identifiers where present.

Notification badge styling

Layer / File(s) Summary
Unread badge colors
ui/components/notificationCenter.tsx
The unread badge uses explicit red background and white text classes, including a dark-mode red background.

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

Merge Risk: ⚪ Minimal · up to e5976

This PR consolidates filter-sidebar controls and adjusts the mobile topbar layout without introducing an evidenced correctness, security, availability, or deployment risk; no actionable merge-blocking risk remains beyond normal checks.

Sequence Diagram(s)

sequenceDiagram
  participant Topbar
  participant TopbarContext
  participant FilterSidebarTrigger
  Topbar->>TopbarContext: Register mobile filter slot
  FilterSidebarTrigger->>TopbarContext: Read mobile filter slot
  FilterSidebarTrigger->>Topbar: Portal mobile filter button
  FilterSidebarTrigger->>FilterSidebarTrigger: Invoke onClick and display active count
Loading

Suggested reviewers: akshaydeo, pratham-mishra04

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
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.
Title check ✅ Passed The title clearly identifies the main change: extracting a shared FilterSidebarTrigger with mobile topbar integration.
Description check ✅ Passed The description covers the required sections and provides clear implementation details and validation steps, despite missing screenshots and unchecked checklist items.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 08-17-fix_responsive_view_fixes_post_notification_center_addition

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

impoiler commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

@impoiler
impoiler force-pushed the 08-17-fix_responsive_view_fixes_post_notification_center_addition branch 2 times, most recently from 5f6cd6f to 3b70410 Compare August 17, 2026 11:31
@impoiler impoiler self-assigned this Aug 17, 2026
@impoiler impoiler changed the title fix: responsive view fixes post notification center addition feat(ui): extract shared FilterSidebarTrigger component with mobile topbar portal Aug 17, 2026
@impoiler
impoiler force-pushed the 08-17-fix_responsive_view_fixes_post_notification_center_addition branch from 3b70410 to c536e7a Compare August 17, 2026 11:33
@impoiler
impoiler force-pushed the 08-17-refactor_hide_notification_icon_unless_any branch from 3090fb2 to a81816b Compare August 17, 2026 11:34
@impoiler
impoiler force-pushed the 08-17-fix_responsive_view_fixes_post_notification_center_addition branch from c536e7a to 0416be5 Compare August 17, 2026 11:34
@impoiler
impoiler marked this pull request as ready for review August 17, 2026 11:34
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 17, 2026
@impoiler
impoiler force-pushed the 08-17-refactor_hide_notification_icon_unless_any branch from a81816b to 6fbf6d1 Compare August 17, 2026 11:39
@impoiler
impoiler force-pushed the 08-17-fix_responsive_view_fixes_post_notification_center_addition branch 2 times, most recently from a2af9e8 to e597603 Compare August 17, 2026 11:41
@impoiler
impoiler force-pushed the 08-17-refactor_hide_notification_icon_unless_any branch from 6fbf6d1 to b2fccde Compare August 17, 2026 11:41

akshaydeo commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Merge activity

@akshaydeo
akshaydeo changed the base branch from 08-17-refactor_hide_notification_icon_unless_any to graphite-base/6232 August 17, 2026 14:36
@akshaydeo
akshaydeo changed the base branch from graphite-base/6232 to dev August 17, 2026 14:44
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review August 17, 2026 14:44

The base branch was changed.

@akshaydeo
akshaydeo requested a review from a team as a code owner August 17, 2026 14:44
@akshaydeo
akshaydeo force-pushed the 08-17-fix_responsive_view_fixes_post_notification_center_addition branch from e597603 to 3f6b1b7 Compare August 17, 2026 14:45
@akshaydeo
akshaydeo merged commit e7a7759 into dev Aug 17, 2026
8 of 10 checks passed
@akshaydeo
akshaydeo deleted the 08-17-fix_responsive_view_fixes_post_notification_center_addition branch August 17, 2026 14:47
akshaydeo pushed a commit that referenced this pull request Aug 19, 2026
… topbar portal (#6232)

## Summary

Extracts the duplicated collapsed-state filter sidebar trigger button into a shared `FilterSidebarTrigger` component and improves the mobile topbar layout so the filter trigger appears inline with the notification bell rather than as a floating overlay.

## Changes

- Added `ui/components/filters/filterSidebarTrigger.tsx` — a new shared component that renders the collapsed filter sidebar trigger. On desktop it renders the existing full-height sidebar rail button. On mobile it portals a compact icon button into a new `mobileFilterSlot` anchor in the topbar, placing it immediately before the notification bell.
- Replaced the duplicated inline `<Button>` collapsed-state blocks in `logsFilterSidebar`, `mcpFilterSidebar`, `mcpLibraryFilterSidebar`, `mcpClientsFilterSidebar`, `mcpSessionsFilterSidebar`, and `oauthGrantsFilterSidebar` with a single `<FilterSidebarTrigger />` call.
- Added `mobileFilterSlot` and `setMobileFilterSlot` to `TopbarContext` and exposed `useMobileFilterSlot` / `useMobileFilterSlotRef` hooks so filter sidebars can portal their mobile trigger into the topbar without the topbar needing to know page-specific content.
- Updated `Topbar` to render the `mobileFilterSlot` anchor span between the left content area and the notification bell, and to show the brand logo on mobile in place of the page title (which is now hidden on small screens).
- Collapsed the user pill on mobile to a bare icon button, hiding the display name and chevron below the `md` breakpoint.
- Changed the notification badge to use explicit `bg-red-600`/`dark:bg-red-700` classes instead of `bg-destructive` to ensure consistent color regardless of theme token overrides.

## Type of change

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

## Affected areas

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

## How to test

```sh
cd ui
pnpm i
pnpm build
```

1. Open any page that has a filter sidebar (Logs, MCP Logs, MCP Library, MCP Clients, MCP Sessions, OAuth Grants).
2. Collapse the filter sidebar and verify the trigger appears correctly on desktop (full-height rail) and mobile (icon in topbar next to the notification bell).
3. Confirm the active filter count badge renders on both breakpoints when filters are applied.
4. Verify the mobile topbar shows the brand logo and a bare user icon, with the full pill restored at the `md` breakpoint.
5. Confirm the notification badge is visually red in both light and dark themes.

## Screenshots/Recordings

Before/after screenshots recommended for the mobile topbar layout and the collapsed filter trigger placement on both breakpoints.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## 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
akshaydeo added a commit that referenced this pull request Aug 23, 2026
…S and lifecycle (#6415)

* V2.0.0 (#4365)

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

* **Bug Fixes**
  * Improved token parameter compatibility handling to preserve alternative formats when the primary option is unsupported.

* **Chores**
  * Version updated to 2.0.0.
  * Enhanced load testing configuration for more reliable builds.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

* fix: bedrock files handling in inference (#5947)

Bedrock was rejecting documents with "The PDF specified was not valid" because the document format was always resolved to `"pdf"` regardless of the actual file type. Standard OpenAI clients encode the MIME type inside the data URL (e.g. `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,...`) rather than in the `file_type` field, which was the only source previously consulted. This PR fixes format resolution for both the Chat and Responses paths, unifies the mapping logic, and corrects several related data URL parsing defects.

Fixes #5472

- Extracted a shared `bedrockDocumentFormat` helper in `utils.go` that maps MIME types and bare file extensions to Bedrock Converse document format strings, replacing two duplicated inline switch blocks that were missing most MIME types.
- Format resolution now follows a priority chain: `file_type` → data URL media type → filename extension → `"pdf"` default. Previously only `file_type` was consulted.
- `ParseDataURL` in `schemas/utils.go` is now a public function that correctly handles media type parameters (e.g. `;charset=utf-8`), uppercase media types, and payloads containing newlines. The old regex silently dropped any data URL whose header contained a parameter, causing the entire `"data:..."` string to be forwarded to Bedrock as the document payload.
- Non-base64 data URLs (e.g. `data:text/plain,Hello%20World`) are now percent-decoded and their text content is populated in both `source.text` and `source.bytes` instead of being forwarded verbatim.
- The Responses path (`responses.go`) previously ignored `file_url` entirely, emitting a document block with an empty source. It now fetches and inlines the bytes the same way the Chat path does, and propagates fetch errors rather than swallowing them.
- `convertBifrostMessageToBedrockMessage` now returns an error instead of silently returning `nil` on conversion failure, so a missing turn is never silently dropped from the request.

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

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

```sh
go test ./core/providers/bedrock/... ./core/schemas/...
```

Key test cases added:

- `TestDocumentFormatFromDataURL` — verifies that each supported MIME type embedded in a data URL resolves to the correct Bedrock format string and that the `data:...` prefix is stripped from `source.bytes`.
- `TestDocumentFormatResolutionPrecedence` — verifies the `file_type` → data URL → filename extension → default priority chain.
- `TestDocumentInlineTextDataURL` — verifies that non-base64 data URLs are percent-decoded and stored in both `source.text` and `source.bytes`.
- `TestToBedrockResponsesRequest_DocumentFormatFromDataURL` — same format fix verified on the Responses path.
- `TestToBedrockResponsesRequest_DocumentFileURLIsFetched` — verifies that an unreachable `file_url` surfaces as an error rather than producing an empty document block.
- `TestParseDataURL` — unit tests for the new public `ParseDataURL` function covering parameters, uppercase, newlines in payload, and invalid inputs.

- [ ] Yes
- [x] No

`file_url` values are now fetched over the network on the Responses path (matching existing Chat path behaviour). The fetch is performed with the existing `providerUtils.FetchAndEncodeURL` helper, which is subject to the same controls already in place for image URL fetching.

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

* fix: add anthropic error branch on stripping on encrypted content (#5960)

Anthropic returns a 400 error with no error code when it rejects a `redacted_thinking` block containing a foreign or invalid payload. The existing `isEncryptedReasoningRejection` detection did not match this error format, causing the retry logic to miss these rejections and fail to strip the offending encrypted content before retrying.

- Extended `isEncryptedReasoningRejection` to also match Anthropic's `redacted_thinking`-specific rejection message: `"Invalid \`data\` in \`redacted_thinking\` block"`.
- Added a comment explaining why this additional check is needed (Anthropic omits an error code and names the offending block in the message text instead).
- Added three new test cases:
  - Confirms the `redacted_thinking` rejection is correctly detected.
  - Confirms that a `thinking` block signature rejection is intentionally **not** matched (since stripping encrypted content would not fix it and would cause an infinite retry loop).
  - Confirms that an unrelated Anthropic 400 mentioning `thinking` (e.g., invalid `budget_tokens`) is not incorrectly matched.

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

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

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

The three new test cases in `TestIsEncryptedReasoningRejection` cover the added detection logic and the intentional non-matches.

- [ ] Yes
- [x] No

No auth, secrets, or PII implications. The change only affects error message pattern matching used to decide whether to strip encrypted reasoning content before retrying a request.

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

* encrypted content patch for encrytped content could not be verified (#6041)

The encrypted-reasoning fail-soft that strips `encrypted_content` from reasoning items before retrying a rejected request only covered `ResponsesRequest`. Bifrost models `/v1/responses/compact` as a separate top-level request shape (`CompactionRequest` with its own `Input` and `RawRequestBody`), so the strip returned `false` for compaction requests, the retry never fired, and the raw `400 invalid_encrypted_content` was handed straight to the client. Codex surfaced this as "Error running remote compact task" and retried the identical body indefinitely.

- Introduced `encryptedReasoningCarriers`, which resolves the `Input` and `RawRequestBody` pointers for whichever of the three Responses-shaped request types (`ResponsesRequest`, `CountTokensRequest`, `CompactionRequest`) is present on a given `BifrostRequest`. This lets a single code path in `stripResponsesEncryptedContent` and `stripRawResponsesEncryptedContent` cover all three shapes without duplicating logic.
- `stripResponsesEncryptedContent` now keys off the resolved pointers rather than a hard check for `ResponsesRequest != nil`, so compaction and token-counting requests are rewritten on the same retry path.
- `stripRawResponsesEncryptedContent` now accepts a `*[]byte` instead of a `*schemas.BifrostResponsesRequest`, removing the coupling to one specific request type.
- Added unit tests covering the compaction strip path for both the structured and raw-body (passthrough) cases, and for the drop-on-empty-summary behaviour on compaction input.
- Added two provider-harness cases under entry 45 that pin the end-to-end behaviour: one where the stripped reasoning item survives (it has a summary), and one where it is dropped entirely (empty summary) rather than forwarded as a bare id the upstream never issued.
- Updated `AGENTS.md` to make explicit that any wire-visible change under `core/` must ship with a provider-harness case, not only bug fixes, and documents the structural validation workflow and the narrow exemptions.

- [x] Bug fix

- [x] Core (Go)

```sh
go test ./core/... -run TestStripResponsesEncryptedContent

node tests/e2e/api/runners/augment-provider-harness.mjs \
  --source tests/e2e/api/collections/provider-harness.json \
  --out tmp/harness-augmented.json

node tests/e2e/api/runners/filter-collection.mjs \
  --source tmp/harness-augmented.json \
  --out tmp/filtered.json \
  --feature "Encrypted Reasoning Fail-Soft on Compaction"
```

The two new harness cases (`openai/gpt-5-mini /openai/v1/responses/compact unverifiable encrypted reasoning degrades` and the summary-less drop variant) should pass structurally. Against a live endpoint they verify that the compaction response returns `object: "response.compaction"` with a non-empty `output` array and no `invalid_encrypted_content` in the body.

N/A

- [x] No

N/A

No auth, secrets, or PII implications. The strip removes `encrypted_content` blobs that the upstream has already refused; no key material is logged or forwarded.

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

* adds path for skipping auth (#6124)

Introduces a `BifrostContextKeySkipProviderCheck` context flag for requests that are evaluated by governance but never routed (e.g., `/inspect`). For these requests, the provider is whatever upstream the caller was already talking to rather than an operator-selected provider, so the virtual key's provider allowlist is meaningless. Without this flag, such requests would be incorrectly blocked by the VK provider gate, and then if that were bypassed, would fail again on a model allowlist that only exists inside a provider config the VK doesn't have.

Also bumps `github.com/bytedance/sonic` from v1.15.1 to v1.15.2 across all modules.

- Added `BifrostContextKeySkipProviderCheck` context key (`bifrost-skip-provider-check`) to `core/schemas/bifrost.go` and registered it as a reserved key in `core/schemas/context.go`.
- `EvaluateVirtualKeyRequest` in `plugins/governance/resolver.go` now accepts a `skipProviderCheck bool` parameter. When set, the provider allowlist gate is skipped. Additionally, if the VK has no provider config for the requested provider (meaning it also has no model allowlist for it), the model gate is skipped as well. A provider the VK does configure retains its model allowlist regardless of the flag.
- `EvaluateGovernanceRequest` in `plugins/governance/main.go` reads the new context key and passes it through to `EvaluateVirtualKeyRequest`.
- `github.com/bytedance/sonic` bumped to v1.15.2 across all modules.
- `google.golang.org/x/crypto`, `x/net`, `x/sync`, `x/sys`, and `x/text` bumped to latest patch versions in test seed modules.
- Node engine constraint removed from `ui/package.json`.

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

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

Two new tests cover the behavior directly:

- `TestBudgetResolver_EvaluateRequest_SkipProviderCheckAllowsUnconfiguredProvider` — verifies that a VK with no config for the requested provider allows the request when the flag is set, and does not fall through to a model block.
- `TestBudgetResolver_EvaluateRequest_SkipProviderCheckKeepsModelAllowlist` — verifies that a provider the VK does configure keeps its model allowlist enforced even when the flag is set.

```sh
cd plugins/governance
go test ./... -run TestBudgetResolver_EvaluateRequest_SkipProviderCheck
```

- [ ] Yes
- [x] No

The `EvaluateVirtualKeyRequest` signature gains a new `skipProviderCheck bool` parameter. All internal call sites have been updated. External callers implementing the interface directly will need to add the parameter.

The flag is intended exclusively for transport-set, read-only inspection paths where the provider is not an operator choice. It bypasses the VK provider and (for unconfigured providers) model allowlists only for those requests. VK existence, active status, expiry, rate limits, and budgets are unaffected. The flag is registered as a reserved context key so it cannot be set by arbitrary plugin or caller code without going through the transport layer.

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

* updates skip-core-test flag and changelog (#6128)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

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

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

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

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

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

* feat(ui): responsive layout improvements across all views (#6105)

## Summary

Add mobile responsiveness to make the dashboard usable on smaller devices. It does not have full coverage, but it includes basic responsiveness so it can be used or at the very least viewed, on mobile screens.

## Changes

- Responsiveness

## Type of change

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

## Affected areas

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

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

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

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [x] No

If yes, describe impact and migration instructions.

## Related issues



## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

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

* feat(ui): add version skew detection with auto-reload and upgrading screen (#6126)

## Summary

Adds graceful version-skew handling so that when Bifrost is being rolled out, users see a clear "upgrading" UI instead of a broken page. Stale asset errors (failed dynamic imports, chunk load failures) are detected, classified, and surfaced through two purpose-built screens: a non-blocking banner for soft failures and a full-page upgrade screen for hard failures. An auto-reload mechanism polls `/api/version` for stability and reloads the page automatically, with a session-storage guard to prevent reload loops.

## Changes

- **`versionSkew.ts`** — New utility module that classifies skew errors by matching known browser/bundler error patterns (`ChunkLoadError`, failed dynamic imports, etc.), maintains a reactive `SkewMode` store (`none | soft | hard`), installs global listeners for `vite:preloadError`, `unhandledrejection`, and asset element errors, and manages a session-storage reload budget (`MAX_AUTO_RELOADS = 2` within a 60-second window) to prevent infinite reload loops.
- **`__updating.tsx`** — New `UpdatingBanner` (non-blocking overlay for soft skew) and `UpdatingScreen` (full-page replacement for hard skew) components. `UpdatingScreen` polls `/api/version` every 3 seconds, requires 3 consecutive matching responses before triggering an auto-reload, and times out after 90 seconds with a manual reload fallback.
- **`__error.tsx`** — `ErrorComponent` now receives the error prop and redirects to `UpdatingScreen` when a skew error is detected, escalating to hard mode via `reportSkew("hard")`.
- **`clientLayout.tsx`** — Adds a `ConfigUnreachable` component shown when the core config fetch fails, with a retry button wired to RTK Query's `refetch`. `FullPage` now receives `hasError`, `isRetrying`, and `onRetry` props to drive this state.
- **`main.tsx`** — Introduces a `Root` component that subscribes to the skew store via `useSyncExternalStore`, renders `UpdatingScreen` on hard skew, overlays `UpdatingBanner` on soft skew, and clears the auto-reload guard after 30 seconds of healthy uptime. Sets `window.__bifrostBooted` to coordinate with the inline boot script.
- **`index.html`** — Adds an inline script that renders a minimal native-HTML upgrading screen if assets fail to load before React boots, using the same session-storage reload guard logic to cap retries.
- **`globals.css`** — Adds the `update-progress` keyframe animation used by the progress bar in `UpdatingScreen`, and fixes a nested media query indentation issue.
- **`versionSkew.test.ts`** — Full test coverage for `isSkewError`, the skew store (subscribe/notify/escalation/downgrade prevention), and the auto-reload guard (budget exhaustion, window expiry, `clearAutoReloadGuard`, and `sessionStorage` unavailability).

## Type of change

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

## Affected areas

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

## How to test

```sh
cd ui
pnpm i
pnpm test
pnpm build
```

To manually verify:

1. Build the UI and serve it, then invalidate a JS asset URL (e.g., rename a chunk file) to trigger a `ChunkLoadError`. The upgrading banner or screen should appear.
2. Reload the page more than twice within 60 seconds while skew is active — the auto-reload should stop and display the manual reload fallback.
3. Simulate a failed `/api/core-config` response; the `ConfigUnreachable` card should appear with a working "Try again" button.

## Screenshots/Recordings

- **Soft skew:** A fixed bottom banner reading "Bifrost is upgrading" with a manual reload button appears without disrupting the current view.
- **Hard skew / boot failure:** A full-page card with an animated progress bar, status text, and "Reload now" button replaces the broken route.
- **Config unreachable:** A card with a `WifiOff` icon and retry button is shown in the main content area.  


**Soft skew**  
  
![image.png](https://app.graphite.com/user-attachments/assets/46f34145-2ad8-4bb9-9d20-4b44ca372213.png)

**Hard skew**

![image.png](https://app.graphite.com/user-attachments/assets/f13237f6-00d1-484a-b246-104837ee096d.png)



## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

The auto-reload guard uses `sessionStorage`, which is scoped to the tab and origin. No auth tokens or PII are stored. The inline boot script in `index.html` is self-contained and does not make authenticated requests.

## Checklist

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

* chore: format UI codebase (#6158)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

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

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

Describe the steps to validate this change. Include commands and expected outcomes.

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

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

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes #123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] 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

* V2.0.0 (#4365)

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

* **Bug Fixes**
  * Improved token parameter compatibility handling to preserve alternative formats when the primary option is unsupported.

* **Chores**
  * Version updated to 2.0.0.
  * Enhanced load testing configuration for more reliable builds.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

* fix: bedrock files handling in inference (#5947)

Bedrock was rejecting documents with "The PDF specified was not valid" because the document format was always resolved to `"pdf"` regardless of the actual file type. Standard OpenAI clients encode the MIME type inside the data URL (e.g. `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,...`) rather than in the `file_type` field, which was the only source previously consulted. This PR fixes format resolution for both the Chat and Responses paths, unifies the mapping logic, and corrects several related data URL parsing defects.

Fixes #5472

- Extracted a shared `bedrockDocumentFormat` helper in `utils.go` that maps MIME types and bare file extensions to Bedrock Converse document format strings, replacing two duplicated inline switch blocks that were missing most MIME types.
- Format resolution now follows a priority chain: `file_type` → data URL media type → filename extension → `"pdf"` default. Previously only `file_type` was consulted.
- `ParseDataURL` in `schemas/utils.go` is now a public function that correctly handles media type parameters (e.g. `;charset=utf-8`), uppercase media types, and payloads containing newlines. The old regex silently dropped any data URL whose header contained a parameter, causing the entire `"data:..."` string to be forwarded to Bedrock as the document payload.
- Non-base64 data URLs (e.g. `data:text/plain,Hello%20World`) are now percent-decoded and their text content is populated in both `source.text` and `source.bytes` instead of being forwarded verbatim.
- The Responses path (`responses.go`) previously ignored `file_url` entirely, emitting a document block with an empty source. It now fetches and inlines the bytes the same way the Chat path does, and propagates fetch errors rather than swallowing them.
- `convertBifrostMessageToBedrockMessage` now returns an error instead of silently returning `nil` on conversion failure, so a missing turn is never silently dropped from the request.

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

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

```sh
go test ./core/providers/bedrock/... ./core/schemas/...
```

Key test cases added:

- `TestDocumentFormatFromDataURL` — verifies that each supported MIME type embedded in a data URL resolves to the correct Bedrock format string and that the `data:...` prefix is stripped from `source.bytes`.
- `TestDocumentFormatResolutionPrecedence` — verifies the `file_type` → data URL → filename extension → default priority chain.
- `TestDocumentInlineTextDataURL` — verifies that non-base64 data URLs are percent-decoded and stored in both `source.text` and `source.bytes`.
- `TestToBedrockResponsesRequest_DocumentFormatFromDataURL` — same format fix verified on the Responses path.
- `TestToBedrockResponsesRequest_DocumentFileURLIsFetched` — verifies that an unreachable `file_url` surfaces as an error rather than producing an empty document block.
- `TestParseDataURL` — unit tests for the new public `ParseDataURL` function covering parameters, uppercase, newlines in payload, and invalid inputs.

- [ ] Yes
- [x] No

`file_url` values are now fetched over the network on the Responses path (matching existing Chat path behaviour). The fetch is performed with the existing `providerUtils.FetchAndEncodeURL` helper, which is subject to the same controls already in place for image URL fetching.

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

* fix: add anthropic error branch on stripping on encrypted content (#5960)

Anthropic returns a 400 error with no error code when it rejects a `redacted_thinking` block containing a foreign or invalid payload. The existing `isEncryptedReasoningRejection` detection did not match this error format, causing the retry logic to miss these rejections and fail to strip the offending encrypted content before retrying.

- Extended `isEncryptedReasoningRejection` to also match Anthropic's `redacted_thinking`-specific rejection message: `"Invalid \`data\` in \`redacted_thinking\` block"`.
- Added a comment explaining why this additional check is needed (Anthropic omits an error code and names the offending block in the message text instead).
- Added three new test cases:
  - Confirms the `redacted_thinking` rejection is correctly detected.
  - Confirms that a `thinking` block signature rejection is intentionally **not** matched (since stripping encrypted content would not fix it and would cause an infinite retry loop).
  - Confirms that an unrelated Anthropic 400 mentioning `thinking` (e.g., invalid `budget_tokens`) is not incorrectly matched.

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

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

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

The three new test cases in `TestIsEncryptedReasoningRejection` cover the added detection logic and the intentional non-matches.

- [ ] Yes
- [x] No

No auth, secrets, or PII implications. The change only affects error message pattern matching used to decide whether to strip encrypted reasoning content before retrying a request.

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

* encrypted content patch for encrytped content could not be verified (#6041)

The encrypted-reasoning fail-soft that strips `encrypted_content` from reasoning items before retrying a rejected request only covered `ResponsesRequest`. Bifrost models `/v1/responses/compact` as a separate top-level request shape (`CompactionRequest` with its own `Input` and `RawRequestBody`), so the strip returned `false` for compaction requests, the retry never fired, and the raw `400 invalid_encrypted_content` was handed straight to the client. Codex surfaced this as "Error running remote compact task" and retried the identical body indefinitely.

- Introduced `encryptedReasoningCarriers`, which resolves the `Input` and `RawRequestBody` pointers for whichever of the three Responses-shaped request types (`ResponsesRequest`, `CountTokensRequest`, `CompactionRequest`) is present on a given `BifrostRequest`. This lets a single code path in `stripResponsesEncryptedContent` and `stripRawResponsesEncryptedContent` cover all three shapes without duplicating logic.
- `stripResponsesEncryptedContent` now keys off the resolved pointers rather than a hard check for `ResponsesRequest != nil`, so compaction and token-counting requests are rewritten on the same retry path.
- `stripRawResponsesEncryptedContent` now accepts a `*[]byte` instead of a `*schemas.BifrostResponsesRequest`, removing the coupling to one specific request type.
- Added unit tests covering the compaction strip path for both the structured and raw-body (passthrough) cases, and for the drop-on-empty-summary behaviour on compaction input.
- Added two provider-harness cases under entry 45 that pin the end-to-end behaviour: one where the stripped reasoning item survives (it has a summary), and one where it is dropped entirely (empty summary) rather than forwarded as a bare id the upstream never issued.
- Updated `AGENTS.md` to make explicit that any wire-visible change under `core/` must ship with a provider-harness case, not only bug fixes, and documents the structural validation workflow and the narrow exemptions.

- [x] Bug fix

- [x] Core (Go)

```sh
go test ./core/... -run TestStripResponsesEncryptedContent

node tests/e2e/api/runners/augment-provider-harness.mjs \
  --source tests/e2e/api/collections/provider-harness.json \
  --out tmp/harness-augmented.json

node tests/e2e/api/runners/filter-collection.mjs \
  --source tmp/harness-augmented.json \
  --out tmp/filtered.json \
  --feature "Encrypted Reasoning Fail-Soft on Compaction"
```

The two new harness cases (`openai/gpt-5-mini /openai/v1/responses/compact unverifiable encrypted reasoning degrades` and the summary-less drop variant) should pass structurally. Against a live endpoint they verify that the compaction response returns `object: "response.compaction"` with a non-empty `output` array and no `invalid_encrypted_content` in the body.

N/A

- [x] No

N/A

No auth, secrets, or PII implications. The strip removes `encrypted_content` blobs that the upstream has already refused; no key material is logged or forwarded.

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

* adds path for skipping auth (#6124)

Introduces a `BifrostContextKeySkipProviderCheck` context flag for requests that are evaluated by governance but never routed (e.g., `/inspect`). For these requests, the provider is whatever upstream the caller was already talking to rather than an operator-selected provider, so the virtual key's provider allowlist is meaningless. Without this flag, such requests would be incorrectly blocked by the VK provider gate, and then if that were bypassed, would fail again on a model allowlist that only exists inside a provider config the VK doesn't have.

Also bumps `github.com/bytedance/sonic` from v1.15.1 to v1.15.2 across all modules.

- Added `BifrostContextKeySkipProviderCheck` context key (`bifrost-skip-provider-check`) to `core/schemas/bifrost.go` and registered it as a reserved key in `core/schemas/context.go`.
- `EvaluateVirtualKeyRequest` in `plugins/governance/resolver.go` now accepts a `skipProviderCheck bool` parameter. When set, the provider allowlist gate is skipped. Additionally, if the VK has no provider config for the requested provider (meaning it also has no model allowlist for it), the model gate is skipped as well. A provider the VK does configure retains its model allowlist regardless of the flag.
- `EvaluateGovernanceRequest` in `plugins/governance/main.go` reads the new context key and passes it through to `EvaluateVirtualKeyRequest`.
- `github.com/bytedance/sonic` bumped to v1.15.2 across all modules.
- `google.golang.org/x/crypto`, `x/net`, `x/sync`, `x/sys`, and `x/text` bumped to latest patch versions in test seed modules.
- Node engine constraint removed from `ui/package.json`.

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

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

Two new tests cover the behavior directly:

- `TestBudgetResolver_EvaluateRequest_SkipProviderCheckAllowsUnconfiguredProvider` — verifies that a VK with no config for the requested provider allows the request when the flag is set, and does not fall through to a model block.
- `TestBudgetResolver_EvaluateRequest_SkipProviderCheckKeepsModelAllowlist` — verifies that a provider the VK does configure keeps its model allowlist enforced even when the flag is set.

```sh
cd plugins/governance
go test ./... -run TestBudgetResolver_EvaluateRequest_SkipProviderCheck
```

- [ ] Yes
- [x] No

The `EvaluateVirtualKeyRequest` signature gains a new `skipProviderCheck bool` parameter. All internal call sites have been updated. External callers implementing the interface directly will need to add the parameter.

The flag is intended exclusively for transport-set, read-only inspection paths where the provider is not an operator choice. It bypasses the VK provider and (for unconfigured providers) model allowlists only for those requests. VK existence, active status, expiry, rate limits, and budgets are unaffected. The flag is registered as a reserved context key so it cannot be set by arbitrary plugin or caller code without going through the transport layer.

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

* feat(gemini): support server-side toolCall/toolResponse parts with thoughtSignature round-trip fidelity (#6071)

Adds support for Gemini's server-side tool invocations (`toolCall`/`toolResponse` parts) that are reported when `toolConfig.includeServerSideToolInvocations` is enabled. Previously, these parts were silently dropped. Now they are parsed, mapped onto Bifrost's neutral `web_search_call` item type (for Google Search variants), and preserved verbatim so the exact parts — including `thoughtSignature` bytes Gemini requires on replay — survive a round-trip through the OpenAI-shaped schema.

- Added `ToolCall` and `ToolResponse` types to `types.go`, with `UnmarshalJSON` implementations that accept both camelCase (Google's REST wire format) and snake_case (google-genai SDK replay format). Both are wired into `Part`'s marshal/unmarshal paths.
- Added `isSearchToolType` and a registry of known search tool type strings (`GOOGLE_SEARCH_WEB`, `GOOGLE_SEARCH_IMAGE`) to distinguish mappable tools from unmapped built-ins like `CODE_EXECUTION`.
- In the non-streaming path (`convertGeminiCandidatesToResponsesOutput`), `toolCall` parts now produce a `web_search_call` item using Gemini's own call ID and queries. A sibling `toolResponse` part marks the item `completed`. When grounding metadata is also present, its sources and any missing queries are merged onto the existing item rather than emitting a duplicate `web_search_call`.
- `thoughtSignature` bytes carried on `toolCall`/`toolResponse` parts are emitted as standalone reasoning items so Gemini can receive them back on replay.
- `serverSideToolParts` stashes the raw `toolCall`/`toolResponse` parts into `ProviderExtraFields["serverSideToolParts"]`. `ToGeminiResponsesResponse` recovers them and prepends them to the candidate's parts, skipping duplicate signature-only parts for signatures already present in those preserved parts.
- In the streaming path (`ToBifrostResponsesStream`), `toolCall` parts record the call ID and queries into new `GeminiResponsesStreamState` fields (`ServerSearchRounds`). At finish, `emitWebSearchFromGroundingMetadata` uses the recorded call ID as the item ID and merges grounding data on top, and now fires even when grounding metadata is absent but a server-side call was observed. Multiple search rounds each produce their own item in the order the model ran them.
- `nativePartPayload` and `nativePartsFromItem` serialize server-side tool parts onto `ResponsesMessage.ProviderNativeParts` so the streaming `/genai` surface can re-emit them byte-for-byte rather than emitting a bare signature-only part.
- `emitWebSearchFromGroundingMetadata` is hardened against nil `metadata` throughout so it can operate on server-side-call-only responses.
- Added `serversidetools_test.go` covering non-streaming scenarios: single search round with grounding merge, unknown tool type preservation, and two search rounds interleaved with a client function call. Added `serversidetools_stream_test.go` covering the streaming path end-to-end, including a full Gemini→Bifrost→Gemini round-trip asserting each `thoughtSignature` appears exactly once.
- Added e2e harness folder 49 covering the Gemini converter path, Vertex converter path (both pre-Gemini-3 and Gemini-3.6), raw passthrough, and a multi-turn replay that lets Gemini validate the `thoughtSignature` bytes server-side.

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

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

```sh
go test ./core/providers/gemini/...
```

The new tests exercise:
- A single server-side Google Search round with grounding metadata: expect exactly one `web_search_call` item carrying Gemini's own call ID, the toolCall's queries, and grounding's sources.
- An unmapped tool type (`CODE_EXECUTION`): expect no `web_search_call` item and the part preserved on the native round-trip.
- Two search rounds interleaved with a client `functionCall`, no grounding metadata: expect two `web_search_call` items paired by ID and the function call unaffected.
- Streaming with two search rounds: expect one item per round in order, each carrying its own call ID and queries, with grounding sources merged onto the first.
- Streaming GenAI round-trip: each `thoughtSignature` appears exactly once across the reconstructed parts; no duplicate signature-only parts alongside native tool parts.

- [ ] Yes
- [x] No

None. The `toolResponse` payload (rendered search-suggestion HTML) is carried opaquely in `ProviderExtraFields` and `ProviderNativeParts` and is not interpreted or executed.

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

* fix(ui): use cached branding in updating screen and flatten border radii/shadows (#6204)

## Summary

Fixes a crash in the updating/version-skew screen caused by calling `useBranding` outside of `<ReduxProvider>`, and standardizes UI border radius styling to use `rounded-sm` instead of larger variants.

## Changes

- Extracted a `getCachedBrandingAssets` function from `useBranding` that reads branding directly from the local cache without requiring Redux store access. The `UpdatingScreen` component now uses this instead of the hook, since it renders above `<ReduxProvider>` and also serves as the router's error component.
- Refactored the shared asset-building logic into a `toBrandingAssets` helper to avoid duplication between `getCachedBrandingAssets` and `useBranding`.
- Replaced `rounded-lg`, `rounded-md`, and `rounded-xl` with `rounded-sm` across the not-found page, updating banner, updating screen, and config-unreachable section for visual consistency.
- Removed `shadow` and `shadow-xl` from several components as part of the same styling pass.

## Type of change

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

## Affected areas

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

## How to test

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

Trigger a version-skew scenario (e.g., deploy a new backend while the UI is open) and confirm the updating screen renders without errors and displays branding correctly. Also verify the not-found and config-unreachable screens render with the updated styling.

## Screenshots/Recordings

Before/after screenshots of the updating screen, not-found page, and config-unreachable section showing the updated border radius and removed shadows.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

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

* adds airgapped flow for mcp catalog (#6195)

## Summary

Extends Bifrost's air-gapped deployment support to cover the MCP server library catalog in addition to the existing pricing and model parameter datasheets. Previously, air-gapped hosts had no way to suppress the catalog fetch or serve it from a local file, causing unnecessary network attempts to `getbifrost.ai` on every sync tick.

## Changes

- **`mcp_library_sync_interval: 0` disables background catalog syncing** — introduces `MCPLibrarySyncDisabled` as an explicit sentinel (mirroring `LiveModelsSyncDisabled`). A zero interval skips the startup fetch and never schedules a background sync, so no requests go to `getbifrost.ai`. Force Sync Now from the UI still works. Negative values continue to be treated as corrupted config and fall back to the default cadence.
- **`file://` URLs for the MCP library catalog** — `fetchMCPLibrary` now resolves file URLs through the shared `datasheet.FilePathFromURL` helper (exported from `sync.go`) so relative forms (`file://./servers.json`, `file:servers.json`) and `file://localhost/...` work identically to how they work for the pricing datasheets.
- **No retry backoff on local file paths** — `SyncMCPLibrary` skips the exponential-backoff retry loop when the URL is a `file://` reference, since a missing local file is not a transient failure and retrying only adds boot latency.
- **Config resolution fixes** — `ResolveFrameworkPricingConfig` previously treated `0` as corrupted and backfilled the default, which would silently re-enable syncing on the next boot. It now passes `MCPLibrarySyncDisabled` through untouched in both the file-config and DB-config paths.
- **Helm chart nil-awareness** — the `mcpLibrarySyncInterval` template condition is updated from a truthiness check to `kindIs "invalid"` so that `0` is correctly written into the rendered config rather than omitted.
- **Schema updates** — both `config.schema.json` and `values.schema.json` now allow `0` as a valid value via `anyOf: [{ const: 0 }, { minimum: 3600 }]`.
- **UI updates** — the MCP Library Settings sheet accepts `file://` URLs, allows a sync interval of `0` (with updated validation message), and preserves `0` through the hours round-trip without collapsing it to the 24h default. MCP Settings page layout is tightened to `max-w-4xl` with consistent padding.
- **Documentation** — the air-gapped guide is restructured into separate Datasheets and MCP server library sections, documents both Option A (local file) and Option B (disable sync), and adds a sync-settings reference table.

## Type of change

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

## Affected areas

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

## How to test

```sh
# Core/Transports
go test ./framework/modelcatalog/... ./transports/bifrost-http/lib/...

# UI
cd ui
pnpm i
pnpm build
```

**Air-gapped datasheet path:**
1. Download `https://getbifrost.ai/mcp-library` to a local file.
2. Set `mcp_library_url: "file:///opt/bifrost/mcp-library.json"` in `config.json`.
3. Start Bifrost — the MCP Library page should populate from the local file with no outbound requests.

**Disabled sync path:**
1. Set `mcp_library_sync_interval: 0` in `config.json`.
2. Start Bifrost — confirm the log line `MCP library sync is disabled (mcp_library_sync_interval=0), skipping startup sync` appears and no requests are made to `getbifrost.ai` on subsequent ticks.
3. Confirm Force Sync Now in the UI still triggers a sync.

**Relative file URL:**
1. Place `servers.json` in the Bifrost working directory.
2. Set `mcp_library_url: "file://./servers.json"` and verify the catalog loads correctly.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

`file://` URL support is limited to paths readable by the Bifrost process user. No new network surface is introduced; the change reduces outbound connections for air-gapped deployments.

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

* adds topbar (#6196)

Introduces a persistent `<Topbar>` component that sits above the inset content card on every page. Page titles are hoisted into it via a lightweight context (`TopbarProvider` / `useSetTopbarTitle`), and page descriptions are portalled into a DOM slot the topbar exposes next to the title — avoiding the re-render loop that would result from storing arbitrary JSX in context state. A new `<PageTitle>` component replaces every inline `<h1>`/`<h2>` + description block across the workspace, rendering nothing inline and instead driving the topbar title and an info-icon hover card.

The external links (Discord, GitHub, bug report, docs) and the user/logout controls that previously lived in the sidebar footer are moved into a topbar dropdown menu, where they are labelled and more discoverable. The sidebar footer is simplified to just the expand affordance for the collapsed rail.

- **`ui/components/topbar.tsx`** — new 48px header strip. Renders the page title (from context or derived from the last path segment with acronym normalisation), a description slot anchor, the theme toggle, and a dropdown menu containing external links and the user/logout action.
- **`ui/lib/contexts/topbarContext.tsx`** — new context providing `useSetTopbarTitle`, `useTopbarTitle`, `useDescriptionSlot`, and `useDescriptionSlotRef`. Title ownership is tracked with a ref so that a mounting page's `setTitle` call is not wiped by the unmounting page's cleanup.
- **`ui/components/pageTitle.tsx`** — new component. Calls `useSetTopbarTitle` and portals an `<Info>` hover card into the topbar's description slot. Renders nothing in the page body.
- **`ui/app/clientLayout.tsx`** — wraps the sidebar provider in `<TopbarProvider>`, inserts `<Topbar>` above the content card, removes the old mobile sticky header (title + `SidebarTrigger`), and adjusts the flex layout so the topbar takes its fixed height and the content card fills the remainder.
- **`ui/components/sidebar.tsx`** — removes external links, theme toggle, user popover, and logout button from the footer. Retains only the collapsed-rail expand button and the promo card stack.
- **`ui/components/themeToggle.tsx`** — extracts `<ThemeToggleItems>` (bare dropdown items with active-state checkmarks) so the items can be embedded in a larger menu. `<ThemeToggle>` now uses them internally.
- **All workspace page/view components** — inline `<h1>`/`<h2>` + description `<p>` blocks replaced with `<PageTitle title="…">description</PageTitle>`. Action buttons that were paired with the heading are moved into the search/filter toolbar row, pushed to the right with `sm:ml-auto`.
- **`tests/integrations/python/config.json`** — removes `env_label` field and collapses single-element JSON arrays onto one line for readability.

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

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

```sh
cd ui
pnpm i
pnpm build
```

1. Open the dashboard and navigate between pages — the topbar should display the correct page title on each route.
2. Pages with a `<PageTitle>` description should show an `ⓘ` icon beside the title; hovering it should reveal the description in a card.
3. The sidebar footer should no longer contain external links, the theme toggle, or the logout button.
4. The topbar menu (hamburger or user pill) should contain Discord, GitHub, bug report, and docs links, plus a "Sign out" entry when auth is enabled.
5. On mobile, the `SidebarTrigger` should appear in the topbar rather than in a sticky in-page header.
6. Theme switching via the topbar toggle should work as before.

Before/after screenshots recommended — the topbar is a visible layout change on every page.

- [ ] Yes
- [x] No

The logout flow and user-info display are unchanged in behaviour; only their render location moved from the sidebar to the topbar dropdown.

- [ ] 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

* adds notification center (#6207)

## Summary

Adds a persistent, role-targeted notification system that allows operators to publish dashboard notifications to all users or specific roles. Notifications are stored in the database, delivered in real-time over WebSocket, and surfaced in the UI via a new notification center in the topbar.

## Changes

- **`core/schemas/notification.go`** — Defines `Notification`, `NotificationInput`, `NotificationSeverity`, `NotificationAudience`, and a `NotificationPublisher` function type shared across the stack.
- **`framework/configstore`** — Adds `TableNotification` GORM model (with JSON-serialized `RoleIDs` to avoid a hard dependency on enterprise role tables), a `NotificationStore` interface, and `CreateNotification` / `ListNotifications` / `DeleteExpiredNotifications` implementations on `RDBConfigStore`. A new migration creates the `notifications` table.
- **`transports/bifrost-http/handlers/notifications.go`** — Introduces `NotificationService` with `Publish`, cursor-paginated `list`, and `create` HTTP handlers (`GET /api/notifications`, `POST /api/notifications`). Input validation enforces title/message length, severity enum, audience/role-ID consistency, and that `action_path` is an internal absolute path. Expired notifications are pruned on startup and hourly.
- **`transports/bifrost-http/handlers/websocket.go`** — `WebSocketClient` now carries `roleID`, `hasRole`, and `localAdmin` fields populated at connection time. `BroadcastNotification` uses these to fan out only to clients whose role matches the notification audience, avoiding unnecessary delivery.
- **`transports/bifrost-http/server/server.go`** — `NotificationService` is instantiated during `Bootstrap` and `RegisterAPIRoutes`; `Config.NotificationPublisher` is wired to `NotificationService.Publish` so other subsystems can publish notifications in-process.
- **UI** — Adds `Notification` and `NotificationListResponse` types, a `notificationsApi` RTK Query endpoint, `localStorage`-backed per-user preference storage (read/dismissed IDs, scoped by user identity), Redux slice actions (`setNotifications`, `addNotification`, `hydrateNotificationPreferences`, `markNotificationRead`, `removeNotification`, `clearAllNotifications`, `markAllNotificationsRead`) with memoized selectors, a `useNotificationSync` hook that hydrates preferences, merges API results, and subscribes to live WebSocket `notification` events, and a `NotificationCenter` popover component mounted in the topbar.

**Design decisions:**
- Read and dismissed state are intentionally local to each UI client (localStorage) rather than persisted server-side, keeping the server schema simple and avoiding per-user state in the OSS database.
- `RoleIDs` is stored as JSON text rather than a relational foreign key so the notifications table works in OSS deployments that do not have an enterprise roles table.
- The list endpoint applies role filtering in the application layer after a bounded DB scan (`maxNotificationScan = 250`) to support role-filtered pagination without complex SQL across optional enterprise tables.
- Cursor pagination encodes `createdAt` (nanosecond Unix timestamp) and `id` as a base64 opaque token.

## Type of change

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

## Affected areas

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

## How to test

```sh
# Core/Transports
go test ./framework/configstore/... ./transports/bifrost-http/handlers/...

# UI
cd ui
pnpm i
pnpm test
pnpm build
```

1. Start the server and open the dashboard.
2. `POST /api/notifications` with a valid `NotificationInput` payload (e.g. `{"audience":"all","severity":"info","title":"Hello","message":"World"}`).
3. Verify the bell icon in the topbar shows an unread badge and the notification appears in the tray.
4. Connect a second browser session with a different role and confirm role-targeted notifications (`audience: "roles"`) are only visible to the matching role.
5. Dismiss or mark notifications as read; confirm state persists across page reloads and is isolated per user.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

- `action_path` is validated to be an internal absolute path (no scheme, no host, must start with `/`), preventing open-redirect payloads from being stored in notifications.
- Role filtering is enforced both at WebSocket broadcast time and at HTTP list time, so users cannot read notifications targeted at other roles.
- Read/dismissed preferences are scoped by user identity (sub, id, or email) to prevent one user's dismissals from affecting another on a shared browser.

## Checklist

- [x] I added/updated tests where appropriate
- [x] I verified builds succeed (Go and UI)

* fix(ui): hide notification center icon until notifications are loaded or open (#6227)

## Summary

The notification center icon in the topbar was visible during initial load even when there were no notifications, causing a brief flash where the icon would appear and then disappear. This PR fixes that glitch by deferring the render of the notification center until there is actually something to show, while also keeping the popover mounted when a user dismisses the last notification so it closes gracefully rather than unmounting mid-interaction.

## Changes

- Added a `useState` hook to track the open/closed state of the notification popover and pass it as controlled state to `<Popover>`.
- Added an early return that hides the notification center trigger when the popover is closed and either the feed is still loading or there are no notifications. This prevents the icon from flashing in and then disappearing on deployments with no notifications.
- The `open` state guard ensures the popover stays mounted while the user is actively working in it, so dismissing the last notification doesn't cause the popover to vanish from under the pointer.
- A failed initial load (which results in an empty list) also benefits from this change — a broken feed hides silently rather than advertising itself, while RTK Query's remount and websocket-push refetch behavior still handles recovery.

## Type of change

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

## Affected areas

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

## How to test

1. Open a deployment with no notifications.
2. Verify the notification bell icon does not appear and then disappear in the topbar during initial load.
3. Open a deployment with existing notifications and confirm the icon appears and the popover opens correctly.
4. Mark all notifications as read or dismiss them one by one and confirm the popover closes cleanly after the last one is dismissed rather than snapping shut mid-interaction.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

## Screenshots/Recordings

Before: The notification icon briefly flashes in the topbar on load for deployments with no notifications.
After: The notification icon only appears once there are notifications to display.

## Breaking changes

- [x] No

## Related issues

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

* feat(ui): extract shared `FilterSidebarTrigger` component with mobile topbar portal (#6232)

## Summary

Extracts the duplicated collapsed-state filter sidebar trigger button into a shared `FilterSidebarTrigger` component and improves the mobile topbar layout so the filter trigger appears inline with the notification bell rather than as a floating overlay.

## Changes

- Added `ui/components/filters/filterSidebarTrigger.tsx` — a new shared component that renders the collapsed filter sidebar trigger. On desktop it renders the existing full-height sidebar rail button. On mobile it portals a compact icon button into a new `mobileFilterSlot` anchor in the topbar, placing it immediately before the notification bell.
- Replaced the duplicated inline `<Button>` collapsed-state blocks in `logsFilterSidebar`, `mcpFilterSidebar`, `mcpLibraryFilterSidebar`, `mcpClientsFilterSidebar`, `mcpSessionsFilterSidebar`, and `oauthGrantsFilterSidebar` with a single `<FilterSidebarTrigger />` call.
- Added `mobileFilterSlot` and `setMobileFilterSlot` to `TopbarContext` and exposed `useMobileFilterSlot` / `useMobileFilterSlotRef` hooks so filter sidebars can portal their mobile trigger into the topbar without the topbar needing to know page-specific content.
- Updated `Topbar` to render the `mobileFilterSlot` anchor span between the left content area and the notification bell, and to show the brand logo on mobile in place of the page title (which is now hidden on small screens).
- Collapsed the user pill on mobile to a bare icon button, hiding the display name and chevron below the `md` breakpoint.
- Changed the notification badge to use explicit `bg-red-600`/`dark:bg-red-700` classes instead of `bg-destructive` to ensure consistent color regardless of theme token overrides.

## Type of change

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

## Affected areas

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

## How to test

```sh
cd ui
pnpm i
pnpm build
```

1. Open any page that has a filter sidebar (Logs, MCP Logs, MCP Library, MCP Clients, MCP Sessions, OAuth Grants).
2. Collapse the filter sidebar and verify the trigger appears correctly on desktop (full-height rail) and mobile (icon in topbar next to the notification bell).
3. Confirm the active filter count badge renders on both breakpoints when filters are applied.
4. Verify the mobile topbar shows the brand logo and a bare user icon, with the full pill restored at the `md` breakpoint.
5. Confirm the notification badge is visually red in both light and dark themes.

## Screenshots/Recordings

Before/after screenshots recommended for the mobile topbar layout and the collapsed filter trigger placement on both breakpoints.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

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

* feat(ui): add service tier column and detail view to logs (#6233)

## Summary

Adds `service_tier` as a tracked field on log entries, enabling cost recomputation to reprice requests at the rates they were actually served at. This surfaces the billing tier (e.g., OpenAI's `"priority"`, `"flex"`, or `"default"`) in both the logs table and the log detail view.

## Changes

- Added `service_tier?: string` to the `LogEntry` type, denormalized onto the log row so cost recomputation can use the correct tier rates.
- Added a `service_tier` column to the logs table, rendering the tier as an uppercase badge when present and `-` when absent.
- Added `"Service Tier"` to the column label map and included `"service_tier"` in the default hidden columns list so it is available but not shown by default.
- Added a `Service Tier` field to the log detail view that renders conditionally when the value is present.

## Type of change

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

## Affected areas

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

## How to test

1. Make a request through a provider that returns a `service_tier` in its response (e.g., OpenAI with `service_tier: "flex"` or `"priority"`).
2. Open the Logs page and enable the **Service Tier** column via the column visibility menu.
3. Verify the tier is displayed as an uppercase badge in the table row.
4. Click into the log entry and confirm the **Service Tier** field appears in the detail view.
5. For a request without a `service_tier`, confirm the column shows `-` and the detail view field is absent.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

_Add before/after screenshots showing the Service Tier column in the logs table and the field in the log detail view._

## Breaking changes

- [x] No

## Related issues

## Security considerations

No security implications. `service_tier` is a non-sensitive billing metadata field.

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

* fix(anthropic): propagate `service_tier` through streaming accumulator to final chunk and log entry (#6236)

## Summary

Anthropic reports `service_tier` on the `message_start` usage block during streaming. The per-event converter drops it, and `BifrostLLMUsage` has no `service_tier` field, so it had nowhere to travel. As a result, every streamed Anthropic request logged an empty `service_tier` and was repriced at standard rates instead of the actual served tier (priority/flex). This fix latches the tier across streaming events and stamps it onto the final chunk's response envelope, mirroring the existing pattern for `speed` and `inference_geo`.

## Changes

- In the Anthropic chat completion and responses streaming loops, `service_tier` from `message_start` usage is now latched into a `servedServiceTier` variable and applied to the final chunk's response envelope, matching how `speed` and `inference_geo` are already handled.
- `StreamAccumulatorResult` gains a `ServiceTier` field so the resolved tier survives the tracer boundary. Without this field, the tier was lost when the accumulator handed off to the tracer, causing streamed rows to reprice at standard rates.
- `ProcessStreamingChunk` in the tracer now copies `ServiceTier` from the processed response into the accumulator result explicitly, since it lives on the response envelope rather than inside `BifrostLLMUsage`.
- `convertToProcessedStreamResponse` in the logging plugin now forwards `ServiceTier` from `StreamAccumulatorResult` into the processed response so `applyStreamingOutputToEntry` can write it to the log entry.
- Tests added to verify the final chunk carries the correct `service_tier` for both the chat completion and responses streaming paths, and that the tier survives the full accumulator-to-log-entry handoff.

## Type of change

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

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)…
@akshaydeo akshaydeo mentioned this pull request Aug 26, 2026
akshaydeo added a commit that referenced this pull request Aug 26, 2026
<Note>
v2.0.0 is the first stable release on the 2.0 line. This changelog rolls
up `2.0.0-prerelease1` (based on
[v1.6.3](https://docs.getbifrost.ai/changelogs/v1.6.3)),
`2.0.0-prerelease2`, `2.0.0-prerelease3` and the final release window,
so it is the complete delta for a deployment upgrading from any v1.6.x
release. Fixes that also shipped on the v1.6.x line after v1.6.3 are
listed once here.
</Note>

<Warning>
**Breaking changes.** Read the [v2.0.0 migration
guide](https://docs.getbifrost.ai/migration-guides/v2.0.0) before
upgrading.

- **Custom plugin downloads are SSRF-protected** - a plugin `path`
pointing at an http(s) URL is rejected if it resolves to a loopback,
private, CGNAT, link-local or otherwise non-public address, and every
custom plugin path is re-verified on each restart, including ones
defined in `config.json`.
- **Custom plugin create and update require admin authentication** -
`POST /api/plugins` and `PUT /api/plugins/{name}` reject a custom `path`
when the caller only got through because dashboard auth is disabled or
unconfigured.
- **Governance APIs moved under `/api/governance/*`** - `/api/teams`,
`/api/users`, `/api/roles`, `/api/audit-logs` and other top-level
governance paths moved under one namespace; Team and User lists use
`limit`/`offset` pagination. Routing rules and the complexity analyzer
moved from `/api/governance/*` to `/api/routing/rules` and
`/api/routing/complexity-analyzer-config`; the old paths remain as
deprecated aliases.
- **`HTTPTransportPreHook` now runs after authentication** - the
pipeline is `HTTPTransportPreAuthHook -> auth -> HTTPTransportPreHook ->
handler`. Plugins that inject a credential (`x-bf-vk`, `Authorization`,
`x-api-key`) must move that work to the new `HTTPTransportPreAuthHook`,
and Go plugins implementing `HTTPTransportPlugin` must add the method
(`.so` plugins that predate it are skipped for that phase).
- **Legacy telemetry attributes removed** - the `gen_ai.*`-namespaced
Bifrost-internal span attributes,
`gen_ai.usage.prompt_tokens`/`completion_tokens`, the nanosecond
`time_to_first_token` attribute and `x-bf-prom-*` request-header
Prometheus dimensions are gone from the OTel and Prometheus connectors.
Dashboards should read the `bifrost.*` keys and `time_to_first_chunk`.
- **Gemini tool preference** - a Gemini API request carrying both
function declarations and Google Search without
`include_server_side_tool_invocations` now keeps the function
declarations and drops Google Search (previously the opposite). Set
`include_server_side_tool_invocations: true` to send both on Gemini 3
models. Vertex is unaffected.
</Warning>

## ✨ Features

- **Batch Accounting** - Provider batch jobs are tracked in a new
`batch_jobs` table and settled asynchronously: results are priced per
model from catalog batch rates (0.5 default ratio) on the `/results`
path, one aggregate cost log is written idempotently with the creating
request's identity, a background sweeper with ownership fencing
re-drives jobs that timed out, settled usage is charged exactly once to
the creating user's budgets and rate limits (including unscoped virtual
key budgets on model-less batch-create requests), mixed-model batch rows
are repriced during cost recalculation, and the log detail view shows a
Batch Details block with per-state request counts and the settled cost
(#5291, #5292, #5293, #5294, #5295, #5296, #6109, #6121, #6376, #6410,
#6474, #6505)
- **Claude-on-Vertex Batches** - Vertex batch jobs route Anthropic
models to `publishers/anthropic/...`, build Claude-on-Vertex JSONL
instances, round-trip `custom_id`, and preserve `tools`, `toolConfig`,
`cachedContent`, `labels` and `display_name` on Gemini/Vertex batch
requests (#5368)
- **Input / Output Cost Split** - Every log carries `input_cost`,
`output_cost` and `additional_cost` (guardrails, semantic cache, MCP)
next to the total, across the RDB, ClickHouse, matviews, recalculation
and the quota API; speech, transcription and OCR usages carry
`BifrostCost`; the log detail view shows the split with per-category
detail (#6511)
- **Bifrost Overhead Latency** - `upstream_latency` and
`overhead_latency` are recorded on every log, aggregated (avg, p90, p95,
p99) in the dashboard's new Bifrost Overhead chart and shown in the log
detail view; the overhead is decomposed by span self-time into
serialization, conversion, plugins, middleware, key selection, queue
wait, networking, client delivery and scheduling buckets (including
streaming per-chunk parse, conversion and backpressure and the worker
hand-off), persisted to `overhead_breakdown` and rendered as a stacked
bar in the log detail view; a `bifrost_overhead_latency_microseconds`
histogram is exported to Prometheus and OpenTelemetry and
`upstream_latency_ms`/`overhead_latency_ms` tags to Maxim, while
breakdown spans are kept out of observability connectors (#5533, #5534,
#5535, #6345, #6388, #6389, #6433, #6470, #6495)
- **Notification Center** - Role-targeted dashboard notifications stored
in the database, delivered over WebSocket and surfaced in a topbar tray
via `GET/POST /api/notifications` (#6207, #6227, #6324)
- **Topbar and Responsive Dashboard** - Persistent topbar with page
titles, theme toggle, external links, user menu and version; responsive
layouts across all views with truncation and tooltips for long values
and icon-only buttons; version-skew detection with an auto-reloading
upgrading screen (#6196, #6105, #6126, #6204, #6232, #6330, #6370,
#6476, #6485, #6493)
- **Video Edits** - `POST /v1/videos/edits` applies prompt-driven edits,
upscaling and background removal to an existing video supplied as bytes,
a URL or a provider video ID, on OpenAI and Runware (#6270)
- **Runware Chat, Catalog and Media Operations** - Chat completions,
streaming and Responses via Runware's OpenAI-compatible endpoint,
`ListModels` from the curated catalog, image upscale via
`/v1/images/edits` (`type=upscale`), image-to-3D and async 3D generation
via `/v1/videos` (`type=3d`), provider-reported per-task cost, and a raw
`/runware_passthrough` route (#6260, #6372, #6208, #6075)
- **JSON Image Edits** - `POST /v1/images/edits` accepts JSON bodies
with URL or base64 images and typed extra params in addition to
multipart (#6418)
- **OpenAI Ultrafast Service Tier** - `service_tier: "ultrafast"` is
forwarded only to models that support it and billed at dedicated
ultrafast rates, with matching custom pricing override fields (#6396,
#6399)
- **Service Tier on Logs** - Logs record the tier actually served,
including Anthropic's `service_tier` from `message_start` on streams,
with a Service Tier column and detail field so repricing uses the served
tier (#6233, #6236)
- **Pricing Fields** - New per-request flat fee (`cost_per_request`),
megapixel-based image tiers (4/8/16/32/64 MP), per-size and joint
size+quality image rates for `gpt-image-1`-style models, and
`input_cost_per_query` for rerank flow through datasheet sync, the cost
engine, custom overrides, the API and the UI override form; upscale
output resolution is backfilled from `target`/`factor` on Replicate so
tiered rates bill the real output size (#6079, #6082, #6083, #6379,
#6380)
- **Model Catalog Pricing and Overrides** - Pricing data in the model
catalog (thanks [@johnbrett](https://github.com/johnbrett)!), with
resolved pricing overrides exposed on `/api/models/details` and on
catalog rows, shown in the dashboard (#6055, #6056, #6058)
- **Typed Embeddings on Bedrock** - Titan V2 `embeddingTypes` and Cohere
`embedding_types` on Converse, the native invoke route and LangChain
`BedrockEmbeddings` (#6381)
- **Rerank Upgrades** - Structured JSON documents, `return_documents`,
`next_token` pagination, caller document IDs preserved in every result,
Cohere-shaped errors, cross-provider responses converted back to the
caller's wire shape, and `/genai/v1/rank` served cross-provider (#6328,
#6301, #6432)
- **OpenRouter Speech, Transcription and Embeddings** - TTS and STT
through OpenRouter's audio endpoints, and embedding models included in
`ListModels` (#5734, #6264)
- **Grok on Bedrock Mantle** - `xai.` models route through the
`openai/v1` Mantle path (#6022)
- **Gemini 3 Thinking Levels** - A per-model `thinkingLevel` support
table clamps requested levels to the rungs each model implements;
`reasoning_effort: "none"` sets the model's floor level instead of
zeroing `thinkingBudget` (#6280)
- **Datasheet-Backed Compatibility** - Anthropic, Bedrock, Cohere and
Gemini request shaping (adaptive thinking, native effort,
disable-reasoning, mid-conversation system turns, computer-use and
text-editor tool generations, default max output tokens, tool
validation) is resolved from model capabilities instead of hardcoded
model-name checks (#6281, #6492)
- **Reasoning Effort None** - Models that reason by default but do not
support reasoning with tool calls get `reasoning.effort: "none"` when
they advertise `supports_none_reasoning_effort`, instead of losing
`reasoning` entirely (#6293)
- **HTTP Transport Pre-Auth Hook** - New `HTTPTransportPreAuthHook`
plugin phase runs before transport authentication so plugins can inject
credentials such as `x-bf-vk`; a `virtual-key-from-config` native plugin
example ships alongside it (#6375, #6373)
- **Plugin Inject Limits** - Per-plugin `semaphore_size` and
`inject_timeout` on `PluginConfig` bound observability `Inject` calls so
a hung connector releases its slot (#6341)
- **Harness Session Autodetection** - Claude Code, Codex CLI and
OpenCode session headers populate the session ID when `x-bf-session-id`
is absent (#6333)
- **Auth and Model Check Skip Paths** - Context keys let trusted
internal callers bypass auth resolution, and let evaluate-only requests
such as `/inspect` bypass the virtual key provider and model allowlists
while budgets and rate limits still apply (#6124, #6479)
- **Passthrough Encoding Negotiation** - Forwarded `Accept-Encoding` is
filtered to decodable codecs (gzip, deflate, brotli, zstd; gzip and
identity for streams) and chained content encodings are decoded (#6360)
- **Routing Plugin** - Routing rules and the complexity router live in a
dedicated `routing` plugin that runs after governance so rules evaluate
on the fully stamped context; endpoints moved to `/api/routing/rules`
and `/api/routing/complexity-analyzer-config` with deprecated
`/api/governance/*` aliases; complexity routing now reads the text of
mixed text+image turns (#6144, #6145, #6146, #6147, #6253)
- **Dimension Scope Ceiling** - Grouped log analytics (rankings,
histograms, key pairs) are bounded to the customer, team, business unit,
user and virtual key ids the caller may see (#6262)
- **MCP Per-User OAuth and Token Exchange** - MCP clients can hold
per-user OAuth credentials and per-user headers, configurable from
`config.json` as well as the UI, with a documented shared vs
per-identity token lookup contract, `oauth_config.resource` (RFC 8707),
VK/Users filters on the OAuth Grants and MCP Auth Sessions sidebars and
one shared create/install client form; `token_exchange` gains
`use_idp_credentials` to reuse SSO login app credentials for providers
such as Microsoft Entra ID (`client_id` becomes optional) and combines
`offline_access` with `<audience>/.default` for Entra OBO; shared-OAuth
clients show `needs_reauth` when their token row is invalidated,
`Reauthorize` is limited to shared clients, the OAuth flow claim is
atomic against concurrent reauth, stored scopes survive a decode
failure, and credential caches propagate cancellation and version their
entries (#6068, #6069, #6078, #6411, #6428, #6429, #6504)
- **MCP Connection Lifecycle and Tool Discovery** - Discovered tools
persist and resync uniformly across all client types through a
hash-gated core callback, surviving restarts and propagating across a
cluster; connections use make-before-break reconnects with ephemeral
clients rebuilt across the whole connect+init retry, last-known tool
maps preserved, connect attempts bound to entry identity and background
reconnects deduped; `needs_session_stickiness` is pinned across
`config.json` reconciliation; updating static headers on a sticky client
pre-flight verifies the new credential and swaps it onto the live
connection, per-call shared-credential clients refresh tools
synchronously, and a failed enable parks the client at `Disabled` so it
can be retried; the global `tool_sync_interval` hot-reloads and re-times
running checkers; state badges render with spaces and the `disconnected`
filter bucket is now `unstable` (#6409, #6430, #6431, #6483, #6502)
- **Air-Gapped MCP Catalog** - `mcp_library_sync_interval: 0` disables
catalog sync and `file://` URLs load the MCP server library from disk
(#6195)
- **MCP Log Redaction and Plugin Logs** - MCP tool logs carry redaction
mappings and plugin logs (#5744, #5746)
- **Splunk Connector Configuration** - `config.schema.json`, Helm values
and dashboard entries for the Splunk HEC observability connector (#6296,
#6091, #6099)
- **Helm Broker Clustering** - `bifrost.cluster.type: broker` with
broker address, port and TLS settings alongside the existing mesh
transport (#6398)
- **HTTP/2 Ping Interval in the UI** - Provider network configuration
exposes `http2_ping_interval_in_seconds` (#6228)
- **Status Code Badges** - Error and passthrough logs show the upstream
HTTP status code in the log detail header (#5536)
- **Server-Side Tool Calls in Logs** - `web_search_call`,
`code_interpreter_call` and similar Responses items render their full
payload in the log detail view (#6475)
- **Gemini Server-Side Tool Calls** - Gemini `toolCall`/`toolResponse`
parts surface as `web_search_call` items with their own call ID and
queries, unmapped tool types are preserved on the native round-trip, and
each `thoughtSignature` appears exactly once on replay (#6071)
- **Bedrock VPC Endpoints** - AWS Bedrock keys can target VPC endpoints
(#6064)
- **W3C Trace ID Propagation** - Requests carry a W3C trace ID on the
context (#5945)
- **Durable Background Jobs** - New `sidekiq` background-job table,
store methods, and runner with recovery and reaper; cost recalculation
migrated to a durable, resumable and cancellable job with polling
instead of SSE (#5800, #5801)
- **Separate OTEL Metrics Pipeline** - The OTEL collector supports a
metrics tab independent of traces, plus separate headers for traces and
metrics (#5939, #5940)
- **Grouped Logs View** - The logs table groups fallback chains under
expandable roots backed by the new `roots_only` filter with child
aggregates, and the model catalog persists tab, search and provider in
the URL (#5522, #5737, #6059)
- **User Agent and App Attribution** - Logs and MCP tool logs record
user agent, app, source, decision, app key and device ID, with custom
user-agent mapping and dashboard dimension rankings; MCP tool logs
observed by the Bifrost Edge agent can be ingested with device, app key,
decision and source attribution
- **S3 Log Export Metadata** - Additional metadata is written alongside
S3 log exports (#6070)
- **Matview Maintenance Off Switch** - `matview_refresh_interval`
accepts `"off"` to disable logstore matview maintenance entirely (thanks
[@jeremym-tanium](https://github.com/jeremym-tanium)!) (#5693)
- **Video Request Info in Logs UI** - Video requests surface their
details in the logs UI (#5946)
- **Shell Rewriter Hook** - The UI handler exposes a `ShellRewriter`
hook for pre-hydration HTML rewriting (#5807)
- **Custom Branding** - Logo and icon branding support with an OSS
fallback stub, cached in localStorage to prevent a logo flash on load
(#5806, #6096)
- **User Assignment on Virtual Keys** - Users can be assigned from the
virtual key sheet (#5863)
- **Quarterly Budgets** - Quarterly budget windows with a configurable
fiscal year start for customers and virtual key provider configs,
surfaced in budget labels (#5996, #5997, #5999, #6115, #6116)
- **Sarvam AI Provider** - Added Sarvam AI as a first-class provider
with chat, text-to-speech, and speech-to-text support (thanks
[@Purvi09](https://github.com/Purvi09)!)
- **ElevenLabs Sound Effects** - Added text-to-sound generation support
via `/v1/sound-generation` (thanks
[@SecretSun](https://github.com/SecretSun)!)
- **Bedrock Project Scoping** - Added optional `project_id` to Bedrock
and Bedrock Mantle key configs with per-alias overrides for Bedrock,
Bedrock Mantle, and Vertex, plus UI support
- **Trace Redaction** - Phase-scoped redaction and revealing, transient
redaction data field for guardrails, and trace content redaction before
connector export
- **Audit Log Object Storage** - S3/GCS object storage config schema for
audit log archival
- **Alerting Configuration** - Alerting schema in `config.schema.json`
with declarative channels and CEL-based rules, Helm chart support, and
enterprise fallback pages
- **Canonical Model Names** - Dashboard model rankings now show
canonical model names instead of inference-profile IDs (thanks
[@satyamkrishna](https://github.com/satyamkrishna)!)
- **OAuth2 Hardening** - Allowlist for private-use redirect URI schemes
(RFC 8252 §7.1) and a `shouldSweep` gate on the OAuth2 sweep worker
- **Mirrored Schema Support** - `schema_url` / `BIFROST_SCHEMA_URL` for
mirrored schema locations in isolated deployments
- **Vertex Single-Region Config** - Enforce single-region configuration
in Vertex key config
- **Helm Chart Updates** - `bifrost.alerting`, audit-log object storage,
`postgresql.external.port` string support, and
`bifrost.mcp.toolGroups[*].id`
- **ChatGPT Passthrough** - Added a ChatGPT passthrough route on the
OpenAI integration with dedicated request handling
- **Edge Fallback Pages** - Added fallback pages for Bifrost Edge
control views (config, devices, inventory) backed by governance resolver
support
- **Agent Handover View** - Added an agent handover page with seeded
end-to-end data support
- **First-Time Setup Token** - A setup token gates first-time setup so a
fresh deployment is not open to the world, and the onboarding checklist
is back, completing its dashboard auth step on SSO deployments (#5759,
#5784, #6322)

## 🐞 Fixed

- **Structured Output Schema Order** - `response_format` JSON schemas
are forwarded byte-for-byte to OpenAI, Anthropic, Bedrock, Gemini and
Cohere so the model generates fields in the caller's declared order
instead of a re-sorted one (#6235)
- **Thinking Block Typing on Streams** - Reasoning items carrying both
an encrypted payload and a visible summary open as `thinking` blocks
instead of `redacted_thinking` (#6292)
- **Replayed Thinking Blocks via `bedrock/` Prefix** - Content-less
`tool_result` blocks are kept, interleaved block order is preserved,
`incomplete` maps to `error` on Converse, and pending reasoning is
consumed by its owning item, so multi-turn tool use no longer wedges
(#6346)
- **Gemini 400s on Claude Code Traffic** - Trailing assistant prefills
are trimmed and mid-conversation system turns are inlined for
Gemini/Vertex; `extra_fields` is echoed on `/anthropic/v1/messages`
(#6363)
- **Bedrock Tool Use IDs** - IDs longer than 64 characters or outside
Bedrock's charset (such as Gemini thought-signature IDs) are aliased
deterministically on both `tool_use` and `tool_result` (#6300)
- **Azure Responses Stream Errors** - Terminal `error` and
`response.failed` events inside an already-open HTTP 200 SSE stream are
surfaced as errors with their nested type, code and message (thanks
[@dani29](https://github.com/dani29)!) (#6302)
- **GenAI SSE Heartbeats** - GenAI streams delimit heartbeat comments so
Google SDK clients preserve the following event, while older openai-go
clients keep the bare heartbeat (thanks
[@dani29](https://github.com/dani29)!) (#6252)
- **OpenCode max_tokens** - `max_tokens` is preserved for
OpenCode-compatible chat endpoints (thanks
[@Alex-wangyang](https://github.com/Alex-wangyang)!) (#6458)
- **HuggingFace Streaming Usage** - HuggingFace is no longer listed as
omitting the `[DONE]` marker, and `stream_options.include_usage`
defaults on its chat streaming path, so streamed calls stop reporting
zero tokens and zero cost (thanks
[@elliottrabac](https://github.com/elliottrabac)!) (#6478)
- **Provider Key Name on Update** - A key PUT that omits `name` no
longer clears it, and already-exists errors keep their constraint detail
(thanks [@cpsc](https://github.com/cpsc)!) (#6417)
- **Bedrock Mantle Streaming** - Bedrock Mantle is registered in
`ProviderSendsDoneMarker` so streams end after `finish_reason` (#6021)
- **URL-Sourced Files and Images** - `gs://` URIs go to Gemini/Gemma as
`fileData.fileUri` and are read from Cloud Storage for Claude-on-Vertex,
`s3://` references go to Bedrock Converse as `s3Location`, Bedrock
rerank synthesizes the foundation-model ARN from a bare model ID, OpenAI
file blocks keep `file_url`, non-http schemes pass through on the OpenAI
and native-Anthropic paths, and Gemini always emits a candidate with its
finish reason and drops payload-free parts (#6239)
- **Together and Alias Pricing** - The management catalog resolves
runtime provider `together` to the datasheet identity and prices
configured aliases through their target model (thanks
[@dani29](https://github.com/dani29)!) (#6257, #6320)
- **Redis Vector Store TAG Escaping** - All RediSearch special
characters are escaped in TAG query values (thanks
[@AdityaPainuli](https://github.com/AdityaPainuli)!) (#5351)
- **MCP Tool Sync Interval Corruption** - Toggling an MCP client's
enable/disable switch no longer corrupts `tool_sync_interval`; the value
is a whole number of minutes, negative values are rejected instead of
silently disabling sync, and re-enabling a per-call client restarts its
discovery cycle (#6409, #6502)
- **MCP Tool Map Staleness** - `SetClientTools` replaces the in-memory
tool map instead of merging, so tools removed upstream leave memory once
the database has dropped them (#6484)
- **SSE Reconnect Identity** - `OnConnectionLost` on SSE MCP clients is
gated on connection identity so a stale connection cannot tear down its
replacement
- **Connector Header Redaction** - `Authorization`, `x-api-key`,
Cloudflare Access and AWS ALB OIDC headers are redacted before export to
every observability backend (#6371)
- **Vertex Mixed Tools** - Vertex AI accepts function declarations and
Google Search in the same request without
`includeServerSideToolInvocations`, and search localization via
`retrievalConfig.latLng` is preserved (#6066)
- **Gemini Tool Preference** - When tool combination is disabled,
function declarations win over Google Search so the model can still call
the caller's tools (#6065)
- **Bedrock Stop Reasons** - Bedrock `content_filter` and
`guardrail_intervened` stop reasons map to `incomplete` status with a
`content_filter` reason
- **Encrypted Reasoning on Compaction** - The fail-soft that strips
`encrypted_content` before retrying a rejected request also covers
`/v1/responses/compact` and count-tokens requests, and recognizes
Anthropic's `redacted_thinking` rejection (#6041, #5960)
- **DAC-Scoped VK Reads** - `from_memory` virtual key reads are blocked
for DAC-scoped callers
- **Path Normalization Auth Bypass** - Fixed a path normalization flaw
that allowed auth to be bypassed (#5763)
- **Minimal Reasoning Effort on GPT-5 Models** - `reasoning_effort:
"minimal"` is preserved for GPT-5-family OpenAI models instead of being
downgraded to `low` (thanks [@jitokim](https://github.com/jitokim)!)
(#6046)
- **Gemini Truncated Response Finish Reason** - Truncated Gemini
responses report `MAX_TOKENS` instead of `OTHER` (thanks
[@AdityaPainuli](https://github.com/AdityaPainuli)!) (#5979)
- **Null Tool-Call Function Name on Streaming** - Streaming continuation
deltas no longer materialize an absent tool-call function name as `null`
(thanks [@AdityaPainuli](https://github.com/AdityaPainuli)!) (#5966)
- **Bedrock Document Uploads** - Fixed Bedrock file handling in
inference so office and PDF documents sent as OpenAI `type: "file"` are
accepted (#5947)
- **xAI Usage Cost** - Fixed USD cost ticks for xAI usage (#5950)
- **Governance List-Models Call** - Budgets and rate limits no longer
trigger a list-models call (#6051)
- **Realtime Response Create Input** - Guarded `response.create` input
(#6050)
- **Governance Rate-Limit Reset CPU** - Guards against invalid reset
timeouts, parallelized resting-budget flows only when absolutely
required, and fixed the calendar-based alignment qualifier
- **Masked Key Persistence** - Never persist masked provider key
previews to config storage (thanks
[@eyeveil](https://github.com/eyeveil)!)
- **OpenShift Arbitrary UIDs** - Build-time group-0 ownership with no
runtime chown (thanks [@eyeveil](https://github.com/eyeveil)!)
- **Passthrough Virtual Key Attribution** - Passthrough calls via the
Azure `api-key` header now attribute to the virtual key (thanks
[@eyeveil](https://github.com/eyeveil)!)
- **Rerank for Custom Providers** - `/v1/rerank` now works with custom
OpenAI-compatible providers (thanks
[@eyeveil](https://github.com/eyeveil)!)
- **Responses Stream Usage** - Persist stream usage when providers omit
or reuse sequence numbers (thanks
[@eyeveil](https://github.com/eyeveil)!)
- **Wildcard allowed_models Repair** - Repair bare wildcard
`allowed_models` rows that broke admin provider updates (thanks
[@eyeveil](https://github.com/eyeveil)!)
- **Streaming Error Panic** - Nil-safe tracing span lookup prevents
panics on streaming errors (thanks
[@eyeveil](https://github.com/eyeveil)!)
- **Anthropic Tool ID Sanitization** - Sanitize `tool_use`/`tool_result`
ids to Anthropic's charset (thanks
[@Shaik-Sirajuddin](https://github.com/Shaik-Sirajuddin)!)
- **Realtime Transcription Sessions** - Support GA transcription-type
sessions in `POST /v1/realtime/client_secrets` (thanks
[@Shaik-Sirajuddin](https://github.com/Shaik-Sirajuddin)!)
- **Diarized Transcription** - Support `diarized_json` segments and
ElevenLabs speaker passthrough (thanks
[@Shaik-Sirajuddin](https://github.com/Shaik-Sirajuddin)!)
- **Model Discovery** - Skip disabled keys when scheduling
model-discovery fetches (thanks
[@Shaik-Sirajuddin](https://github.com/Shaik-Sirajuddin)!)
- **MCP Timeout Placeholder** - Show the real global default in the MCP
tool execution timeout placeholder (thanks
[@Shaik-Sirajuddin](https://github.com/Shaik-Sirajuddin)!)
- **Redacted Thinking Round-Trip** - Round-trip Anthropic
`redacted_thinking` blocks on the Responses surface (thanks
[@fus3r](https://github.com/fus3r)!)
- **Streaming Accumulation** - Preserve citation annotations and
`finish_reason` in the accumulated streaming response (thanks
[@fus3r](https://github.com/fus3r)!)
- **Gemini Grounded Streaming** - Reset web-search flag when recycling
pooled stream state so `web_search_call` items keep emitting (thanks
[@fus3r](https://github.com/fus3r)!)
- **Bedrock Truncation Signal** - Signal `max_output_tokens` truncation
on the Responses API (thanks
[@jeremym-tanium](https://github.com/jeremym-tanium)!)
- **Bedrock Reasoning Config** - Preserve `reasoning_config` on
cross-provider translation so fallbacks keep extended thinking (thanks
[@Purvi09](https://github.com/Purvi09)!)
- **Anthropic tool_search** - Forward and rebuild server-side
`tool_search` on the Responses path (thanks
[@ws4charlie](https://github.com/ws4charlie)!)
- **OpenAI Responses Input** - Strip `role` from non-message input items
(thanks [@nettee](https://github.com/nettee)!) and serialize compaction
request `input` correctly (thanks
[@mcclurmc](https://github.com/mcclurmc)!)
- **additional_tools Support** - Added `additional_tools` message type
support, preserving nested tool types on `/v1/responses`
- **Plugin Stream Errors** - Emit structured plugin stream errors on
integration routes (thanks [@jeffhos](https://github.com/jeffhos)!)
- **Pooled Object Hygiene** - Zero pooled ChannelMessage references on
release and sweep orphaned deferred spans in trace store TTL cleanup
(thanks [@citrocat](https://github.com/citrocat)!)
- **Hybrid Log Token Usage** - Rebuild token usage from denormalized
columns in hybrid log list (thanks [@G-XD](https://github.com/G-XD)!)
- **MCP Tool Ordering** - Deterministic MCP tool ordering for prompt
cache stability
- **MCP Inline-Auth Links** - Warn callers not to truncate the `#t=`
temp-token fragment (thanks
[@MarcusPeng](https://github.com/MarcusPeng)!)
- **Gemini Fixes** - Web search options map to Google Search grounding,
file upload MIME types preserved, and video reference fields map to
instances (thanks [@vojthor](https://github.com/vojthor)!)
- **OpenAI Parameters** - Honor service tier in chat completion and cap
max reasoning effort
- **Anthropic Costing** - Correct inference geo cost and cache rate for
fast mode
- **SecretVar Parsing** - Parse `SecretVar` JSON with `ref`/`env_var`
fields even when `value` is absent
- **Telemetry** - Forward request id and trace id, reduce metrics
cardinality explosion risk, and send status codes on OTEL metrics
- **Dashboard** - Preserve active time period when applying dimension
filters, adjust bucket size thresholds for month-range durations, show
user popover with `preferred_username` fallback, filter provider-level
keys from the prompt manager selector (thanks
[@rlex](https://github.com/rlex)!), skip password validation for
redacted credentials, and improve `ModelMultiselect` empty and error
states
- **API Key Provider Selection** - Fixed provider selection for API keys
- **Azure Auth Headers** - Pass Azure auth headers in helpers
- **Stream Delta Schema** - Added `ExtraContent` to
`ChatStreamResponseChoiceDelta` (thanks
[@nghodkicisco](https://github.com/nghodkicisco)!)
- **API Auth Bypass** - Stopped `/api/devices` bypassing auth via the
`/api/dev` prefix
- **Bedrock Error Types** - Surface the AWS exception type
(`X-Amzn-Errortype`) on non-streaming Bedrock error responses instead of
dropping it

## 🔧 Maintenance

- **Hot-Path Performance** - Cached serialization for shared MCP tools,
a direct `OrderedMap` JSON writer, bulk span attribute writes with
cached span pointers, reusable worker delivery timers, retained span
attribute maps, generation-stamped memoization of `GetProvidersForModel`
and `GetModelsForProvider` via the new `gencache` package, sonic-based
JSON responses, and a plugin-log existence check before draining (#6242,
#6241, #5956, #5957, #5657, #6387, #5641, #6224, #6268, #6211)
- **Go Toolchain** - Modules build with Go 1.26.6 and the Nix flake pins
1.26.7 (#6269, #6385)
- **Dependency Upgrades** - Dependabot updates across all modules,
newman 6.2.2 with pinned transitive overrides, module path fixes and
`openai_config` referenced from every provider config schema (#6040,
#5864, #6267, #6305, #6275)
- **Test Coverage** - vLLM instances provisioned on RunPod in the
release pipeline, Runware harness coverage including `/v1/images/edits`
and `/v1/videos`, batch and pricing-override lifecycle harness cases, an
Anthropic `message_start` usage regression test, LangChain rerank and
embedding integration tests, and e2e fixes for dashboard auth, budget
reset and MCP state (#5541, #6303, #6319, #6299, #6327, #6432, #6351)
- **Documentation** - v2.0.0 migration guide with the governance
namespace mapping and a v1.5.x downgrade guide for `prerelease3`
deployments, v2.0.0 availability callouts, routing API namespace docs,
Bedrock application inference profiles, Splunk connector docs,
config.schema.json and Datadog env var reference fixes, and Discord
badge fixes (thanks [@Swpn0neel](https://github.com/Swpn0neel)!) (#6332,
#6374, #6420, #6147, #6203, #6099, #5938, #6019, #6425, #6448)
- **Helm** - Chart releases v2.1.35 and v2.1.36 (#6129, #6249)
- **Governance Route Families** - Editions can override governance route
families (#5839)

## 🗄️ Database Migrations

All migrations below are new relative to v1.6.11. Deployments on an
older v1.6.x release should also review the intermediate v1.6.x
changelogs.

**configstore:**

- **add_mcp_client_pending_oauth_config_json_column** - Adds
`pending_oauth_config_json` to `config_mcp_clients`. Reversible: drops
the added column.
- **merge_oauth_token_tables** - Consolidates `oauth_tokens` and
`oauth_user_tokens` into `mcp_oauth_tokens`. **Non-reversible**:
rollback deliberately leaves `mcp_oauth_tokens` in place, because every
OAuth read and write targets it from this migration onward and dropping
it would destroy any token created or refreshed since, forcing every
holder to re-authorize.
- **create_mcp_oauth_flows_table** - Creates `mcp_oauth_flows` to track
in-flight OAuth flows. Reversible: drops the new table.
- **drop_oauth_config_pkce_columns** - Drops CSRF state, PKCE verifier
and `expires_at` from the OAuth config table now that they live on
`mcp_oauth_flows`. **Non-reversible**: forward-only, the dropped values
were per-flow ephemeral and re-adding empty columns would restore
nothing.
- **drop_oauth_config_token_id_column** - Drops `token_id`.
**Non-reversible**: forward-only, it was a pure FK shortcut now
reachable via `(oauth_config_id, auth_mode)`.
- **add_mcp_admin_auth_mode_indexes** - Adds admin partial unique
indexes on `mcp_oauth_tokens` and `mcp_per_user_header_credentials`.
Reversible: drops both indexes.
- **add_mcp_client_token_exchange_json_column** - Adds
`token_exchange_json` to `config_mcp_clients`. Reversible: drops the
added column.
- **add_needs_session_stickiness_column** - Adds
`needs_session_stickiness` to `config_mcp_clients`. Reversible: drops
the added column.
- **add_bedrock_endpoints_columns** - Adds Bedrock VPC endpoint columns
to the keys table. Reversible: drops the added columns.
- **add_cost_per_request_pricing_column** - Adds `cost_per_request` to
model pricing. Reversible: drops the added column.
- **add_notifications_table** - Creates the `notifications` table for
the dashboard notification center. Reversible: drops the table.
- **add_batch_jobs_table** - Creates `batch_jobs` with a unique
`(provider, batch_id)` identity index, a sweeper scan index and a
runner-id index. Reversible: drops the table.
- **add_image_megapixel_tier_pricing_columns** - Adds the five
`output_cost_per_image_above_{4,8,16,32,64}_megapixels` columns to model
pricing. Reversible: drops the added columns.
- **add_input_cost_per_query_column** - Adds `input_cost_per_query` to
model pricing for rerank. Reversible: drops the added column.
- **add_ultrafast_pricing_columns** - Adds the four `*_ultrafast` token
rate columns to model pricing. Reversible: drops the added columns.
- **add_image_size_quality_pricing_columns** - Adds the 14 per-size and
size+quality image output rate columns to model pricing. Reversible:
drops the added columns.
- **add_batch_jobs_attribution_columns** - Adds `user_id`, `team_id`,
`customer_id` and `source_log_id` to `batch_jobs` plus a `user_id`
index. Reversible: drops the index and the four columns.

**logstore:**

- **logs_add_guardrail_debug_column** - Adds `guardrail_debug` to logs.
Reversible: drops the added column.
- **mcp_tool_logs_add_redaction_mapping_column** - Adds the redaction
mapping column to MCP tool logs. **Non-reversible**: rollback is a no-op
because dropping the column would permanently destroy reveal data for
already-redacted MCP logs.
- **logs_add_user_agent_column** - Adds user agent and app columns,
their indexes, and a `UserAgentMapping` table. Reversible: drops the
indexes and the mapping table.
- **mcp_tool_logs_add_user_agent_column** - Adds user agent and app
columns plus indexes to MCP tool logs. Reversible: drops both indexes
and the `app` column.
- **logs_recreate_matviews_with_app_column** - Recreates the log
materialized views to include the user agent and app columns. Rollback
is a no-op because `ensureMatViews` recreates them on next startup.
- **mcp_tool_logs_add_endpoint_columns** - Adds `source`, `decision`,
`app_key` and `device_id` to MCP tool logs. Reversible: drops all four
columns.
- **mcp_tool_logs_add_plugin_logs_column** - Adds `plugin_logs` to MCP
tool logs. Reversible: drops the added column.
- **logs_add_video_edit_input_column** - Adds `video_edit_input` to
logs. Reversible: drops the added column.
- **logs_add_upstream_and_overhead_latency_columns** - Adds
`upstream_latency` and `overhead_latency` to logs. Reversible: drops
both columns.
- **logs_add_batch_debug_column** - Adds `batch_debug` to logs.
Reversible: drops the added column.
- **logs_add_cost_breakdown_columns** - Adds `input_cost`, `output_cost`
and `additional_cost` to logs. Reversible: drops the three columns.
- **logs_recreate_matviews_with_cost_breakdown** - Marks the hourly
matview for rebuild with the cost split columns; `repairMatViewShapes`
drops and recreates `mv_logs_hourly` on the next startup. Rollback is a
no-op because `ensureMatViews` recreates it on next startup.
- **logs_add_overhead_breakdown_column** - Adds `overhead_breakdown` to
logs. Reversible: drops the added column.

<Warning>
**High-throughput deployments: run the logstore migrations during a
low-activity window.**

Every logstore migration above alters `logs` or `mcp_tool_logs`, the two
highest-insert tables in Bifrost, and several also build indexes on
them. On a busy instance the index builds hold locks that block
concurrent log inserts for the duration of the build, and the matview
recreations rebuild against the full table. Schedule the upgrade for a
low-traffic period, or expect elevated log-write latency and possible
request-path backpressure while the migrations run.
</Warning>

<Warning>
`merge_oauth_token_tables`, `drop_oauth_config_pkce_columns` and
`drop_oauth_config_token_id_column` transform or remove existing OAuth
state and cannot be rolled back. Take a database backup before
upgrading, and do not roll the binary back past this release once the
migration has run.
</Warning>

## 🐙 Closed GitHub Issues

- [#123](#123) - Files API
Support
- [#2347](#2347) - MCP tool
ordering is non-deterministic, breaking prefix-based prompt caching
- [#3455](#3455) - Segfault/nil
dereference panic in Bedrock provider
- [#4318](#4318) -
allowed_models persisted as bare "*" string blocks subsequent provider
updates
- [#4353](#4353) - config.db
corruption from masked-key preview in provider_configs JSON column
- [#4367](#4367) - Image
incompatible with OpenShift arbitrary UIDs
- [#4402](#4402) - Vertex
provider drops image blocks whose URL uses gs:// scheme
- [#4477](#4477) - Passthrough
calls using a Virtual Key log as actual key
- [#4679](#4679) - Bedrock
Responses API does not signal max_output_tokens truncation
- [#4689](#4689) - Custom
providers cannot set budget
- [#4712](#4712) - ElevenLabs
sound effects (/v1/sound-generation)
- [#4780](#4780) - Anthropic
server-side tool_search results are dropped on /v1/responses
- [#4834](#4834) - /v1/rerank
is not available with custom providers
- [#4846](#4846) - Responses
stream usage present in response.completed but not persisted in LLM Logs
- [#4851](#4851) - Governance
rate-limit reset causes high CPU in BumpRateLimitUsage
- [#4870](#4870) - Pooled
ChannelMessage retains request body, context, and undelivered response
while idle
- [#4940](#4940) - Show
canonical model names instead of Bedrock inference-profile IDs in Model
Rankings
- [#4963](#4963) - Streaming
finish_reason dropped from the accumulated (logged) response
- [#5002](#5002) -
gpt-4o-transcribe-diarize transcription fails due to string segment IDs
- [#5013](#5013) - OpenAI
/responses/compact input serialized as a JSON object causing 400
- [#5026](#5026) - [Bug]:
Toggling an MCP client's enable/disable switch corrupts its
tool_sync_interval (nanoseconds resent as minutes)
- [#5027](#5027) - MCP Tool
Execution Timeout placeholder shows 0 instead of real global default
- [#5036](#5036) - Plugin
StreamInterceptionError is flattened on integration routes
- [#5037](#5037) - Disabled
keys break provider model discovery
- [#5051](#5051) - Add Sarvam
AI provider (chat + TTS/STT)
- [#5061](#5061) - Streaming
responses drop citation annotations from the accumulated message
- [#5093](#5093) - Streaming
/v1/responses drops Anthropic redacted_thinking blocks
- [#5097](#5097) - Anthropic
rejects replayed tool_use/tool_result ids from non-conforming upstream
providers
- [#5100](#5100) -
additional_tools loses nested tool types on /v1/responses
- [#5101](#5101) -
Chat-to-Responses tool replay sends role on function_call input items
- [#5108](#5108) - Bedrock
reasoning_config silently dropped on cross-provider translation
- [#5113](#5113) -
Gemini/Vertex streaming stops emitting web_search_call items after first
grounded request
- [#5432](#5432) - Add TTS and
STT support for OpenRouter
- [#5472](#5472) - [Bug]:
Bedrock rejects office/PDF document uploads via OpenAI `type:"file"` -
"The PDF specified was not valid"
- [#5871](#5871) - [Bug]: AWS
Bedrock Mantle streaming is broken
- [#5874](#5874) - [Bug]: SSE
heartbeat frame aborts streams for openai-go ssestream consumers (<
v3.43.0) with "unexpected end of JSON input"
- [#5885](#5885) - [Bug]:
v1.6.8 omits message_start.message.usage on Bedrock-backed providers,
breaking @ai-sdk/anthropic streaming
- [#5900](#5900) - [Bug]:
Streaming continuation chunks materialize omitted tool-call metadata as
null
- [#5978](#5978) - [Bug]:
Gemini egress reports truncated responses as FinishReason OTHER,
IncompleteDetails switch matches a string that never occurs
- [#6044](#6044) - [Bug]:
normalizeOpenAIReasoningEffort maps 'minimal' to 'low' for ALL OpenAI
models, even ones that natively support 'minimal'
- [#6240](#6240) - [Bug]: GenAI
SSE heartbeat framing causes @google/genai to silently drop the
following data event
- [#6248](#6248) - [Bug]:
OpenRouter embedding models missing from Semantic Cache dropdown
- [#6334](#6334) - [Bug]:
Gemini/Vertex provider fails on Claude Code assistant prefills and
mid-conversation system turns (Gemini 3.6 Flash & 3.7 Flash HTTP 400)
- [#6342](#6342) - [Bug]:
Anthropic ingress with bedrock/ prefix restructures replayed thinking
blocks, wedging multi-turn tool use on claude-opus-4-8
- [#6416](#6416) - [Bug]:
Provider key update silently clears "name" when omitted, then the
unique-name index 409s subsequent updates
- [#6457](#6457) - [Bug]:
OpenCode chat endpoints drop max completion limit
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