Skip to content

fix: add anthropic error branch on stripping on encrypted content - #5960

Merged
akshaydeo merged 1 commit into
devfrom
08-07-fix_add_anthropic_error_branch_on_stripping_on_encrypted_content
Aug 10, 2026
Merged

fix: add anthropic error branch on stripping on encrypted content#5960
akshaydeo merged 1 commit into
devfrom
08-07-fix_add_anthropic_error_branch_on_stripping_on_encrypted_content

Conversation

@TejasGhatte

@TejasGhatte TejasGhatte commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

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.

Changes

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

Type of change

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

Affected areas

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

How to test

go test ./core/...

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

Breaking changes

  • Yes
  • No

Security considerations

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.

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

@CLAassistant

CLAassistant commented Aug 7, 2026

Copy link
Copy Markdown

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

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of Anthropic responses with invalid encrypted or redacted reasoning data.
    • Prevents these responses from being treated as unexpected errors, while preserving existing behavior for unrelated validation issues.

Walkthrough

The encrypted reasoning rejection classifier now recognizes Anthropic errors for invalid data in a redacted_thinking block. Tests cover classifier behavior and fail-soft retries for Anthropic and OpenAI Responses API requests.

Changes

Encrypted reasoning rejection handling

Layer / File(s) Summary
Extend rejection classification and coverage
core/encryptedreasoning.go, core/encryptedreasoning_test.go
The classifier recognizes Anthropic redacted_thinking invalid-data messages. Tests confirm that invalid thinking signatures and unrelated thinking validation errors remain unhandled.
Validate provider fail-soft behavior
tests/e2e/api/collections/provider-harness.json
Collection 44 adds Anthropic non-streaming, Anthropic streaming, and OpenAI non-streaming tests. The tests verify successful retries, removed encrypted payloads, and streaming completion.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

Suggested reviewers: akshaydeo, pratham-mishra04, roroghost17

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the Anthropic error-handling fix for encrypted content stripping.
Description check ✅ Passed The description covers the bug, changes, tests, affected areas, breaking changes, security, and checklist with only minor template sections omitted.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 08-07-fix_add_anthropic_error_branch_on_stripping_on_encrypted_content

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

TejasGhatte commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai
coderabbitai Bot requested a review from akshaydeo August 7, 2026 17:32
@TejasGhatte
TejasGhatte force-pushed the 08-07-fix_add_anthropic_error_branch_on_stripping_on_encrypted_content branch from 3d97692 to cbf9362 Compare August 7, 2026 17:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/e2e/api/collections/provider-harness.json`:
- Line 49670: Update the response guard conditions in the affected collection
tests at the three occurrences to skip only expected credential or quota
statuses (401, 403, and 429), allowing 5xx responses to reach assertions. Ensure
the fail-soft assertions require the response code to be below 400.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 98583792-3565-468e-a946-9d12851b4ce4

📥 Commits

Reviewing files that changed from the base of the PR and between 3d97692 and cbf9362.

📒 Files selected for processing (1)
  • tests/e2e/api/collections/provider-harness.json

Comment thread tests/e2e/api/collections/provider-harness.json
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 7, 2026

akshaydeo commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Merge activity

@TejasGhatte
TejasGhatte force-pushed the 08-07-fix_cost_in_usd_ticks_for_xai_usage branch from 1565566 to a4bd159 Compare August 10, 2026 15:27
@TejasGhatte
TejasGhatte force-pushed the 08-07-fix_add_anthropic_error_branch_on_stripping_on_encrypted_content branch from cbf9362 to 3aeef95 Compare August 10, 2026 15:27
@akshaydeo
akshaydeo changed the base branch from 08-07-fix_cost_in_usd_ticks_for_xai_usage to graphite-base/5960 August 10, 2026 16:41
@akshaydeo
akshaydeo changed the base branch from graphite-base/5960 to dev August 10, 2026 16:42
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review August 10, 2026 16:42

The base branch was changed.

@akshaydeo
akshaydeo force-pushed the 08-07-fix_add_anthropic_error_branch_on_stripping_on_encrypted_content branch from 3aeef95 to 6b08c09 Compare August 10, 2026 16:42
@akshaydeo
akshaydeo merged commit d025e9c into dev Aug 10, 2026
14 of 15 checks passed
@akshaydeo
akshaydeo deleted the 08-07-fix_add_anthropic_error_branch_on_stripping_on_encrypted_content branch August 10, 2026 16:45
akshaydeo pushed a commit that referenced this pull request Aug 10, 2026
)

## Summary

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.

## Changes

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

## Type of change

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

## Affected areas

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

## How to test

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

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

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

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.

## Checklist

- [ ] 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
This was referenced Aug 11, 2026
akshaydeo pushed a commit that referenced this pull request Aug 13, 2026
)

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
akshaydeo pushed a commit that referenced this pull request Aug 13, 2026
)

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
@coderabbitai coderabbitai Bot mentioned this pull request Aug 13, 2026
18 tasks
akshaydeo pushed a commit that referenced this pull request Aug 14, 2026
)

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
@coderabbitai coderabbitai Bot mentioned this pull request Aug 18, 2026
11 tasks
akshaydeo pushed a commit that referenced this pull request Aug 19, 2026
)

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
akshaydeo pushed a commit that referenced this pull request Aug 19, 2026
)

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
akshaydeo pushed a commit that referenced this pull request Aug 19, 2026
)

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
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)…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants