Skip to content

fix: include fallbacks when preparing transcription requests - #4011

Open
mmelvin0 wants to merge 56 commits into
maximhq:devfrom
mmelvin0:transcription-fallbacks
Open

mmelvin0 wants to merge 56 commits into
maximhq:devfrom
mmelvin0:transcription-fallbacks

Conversation

@mmelvin0

@mmelvin0 mmelvin0 commented Jun 3, 2026

Copy link
Copy Markdown

Summary

This fixes an issue where the fallbacks parameter ignored by transcription endpoints.

This affects transcription requests and governance routing rules fallback behavior.

It seems reading the fallbacks field was missed when internally preparing a transcription request. It explicitly added by governance/routing rules here and here and implicitly allowed as a form field here.

Additionally, there's an impedance mismatch between JSON requests map[string]any and multipart/form-data map[string][]string. Because multiple fallbacks get serialized as a JSON array by WriteMultipartField(), parseFallbacks() doesn't work as intended.

Changes

  • ParseMultipartFormFields() now extracts multiple fields values with the same name to []string instead of only the last one as string.
  • WriteMultipartField() now encodes []string as multiple form values under the same name instead of as JSON.
  • prepareTranscriptionRequest() no longer ignores the fallbacks.

Type of change

  • Bug fix

Affected areas

  • Core
  • Transports (HTTP)

How to test

I tested this manually with both request-driven (e.g. curl -F fallback=provider/model and governance-drive and every combination I could think of for my use cases.

Open to guidance as to how to add good tests for this. Current Go unit tests have some failures on clean dev branch for me.

Breaking changes

  • No

If you happened to have a transcription endpoint using the fallback feature, it wasn't working before and will start working now.

Related issues

Closes #4005

Checklist

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

Summary by CodeRabbit

  • Bug Fixes

    • Repeated multipart form text fields are now preserved and aggregated instead of being overwritten.
    • Multipart reconstruction avoids writing duplicate form fields when payload overrides are applied.
    • Multi-value fields are serialized as multiple form entries rather than a single encoded value.
  • New Features

    • Transcription requests submitted via multipart forms can include parsed fallback options.

@CLAassistant

CLAassistant commented Jun 3, 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 all sign our Contributor License Agreement before we can accept your contribution.
8 out of 11 committers have signed the CLA.

✅ akshaydeo
✅ BearTS
✅ roroghost17
✅ impoiler
✅ mmelvin0
✅ axelray-dev
✅ yanhao98
✅ alexef
❌ TejasGhatte
❌ Pratham-Mishra04
❌ stepsecurity-app[bot]
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Preserve repeated multipart text fields as strings or aggregated []string; emit each []string element as its own multipart field; avoid duplicate writes during reconstruction; parse multipart fallbacks and include parsed Fallbacks and Provider in transcription requests.

Changes

Fallback Handling in Transcription

Layer / File(s) Summary
Multipart field aggregation and serialization
core/network/multipart.go
ParseMultipartFormFields aggregates repeated non-file form fields into []string on second occurrence; WriteMultipartField emits one multipart field per []string element.
Reconstruction skip duplicate fields
core/network/multipart.go
ReconstructMultipartBody closes current part and skips writing a payload-derived replacement when the same field name was already written earlier.
Tests: expect []string for repeated fields
core/network/multipart_test.go
TestWriteMultipartField updated to assert parsed["tags"] is a []string when multiple tags values are written.
Fallback parsing and transcription request wiring
transports/bifrost-http/handlers/inference.go
prepareTranscriptionRequest reads form.Value["fallbacks"], calls parseFallbacks (returns an error on parse failure), and includes parsed Fallbacks and the resolved Provider in schemas.BifrostTranscriptionRequest.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • #3408: Multipart fallbacks parsing and propagation relate to routing-rule fallback leakage and ensuring fallbacks reach transcription requests.

Possibly Related PRs

  • maximhq/bifrost#3924: Also touches multipart fallbacks handling and router-side filtering of fallbacks via available providers.

Suggested Reviewers

  • akshaydeo

Poem

🐰 In fields where values used to hide,
I gather repeats and keep them side by side.
I write each tag as its own small song,
skip doubled echoes that don't belong.
Fallbacks read and wired along. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: include fallbacks when preparing transcription requests' accurately and concisely describes the main change in the PR: fixing the bug where fallbacks were being ignored in transcription request preparation.
Description check ✅ Passed The PR description covers the key required sections: Summary explaining the problem, Changes detailing what was modified, Type of change marked as Bug fix, Affected areas identified, Testing approach described, and Related issues linked. Minor gaps exist (no screenshots applicable, tests not added but acknowledged), but overall compliance is strong.
Linked Issues check ✅ Passed The PR implementation fully addresses the linked issue #4005: code changes preserve fallback information through the request pipeline (ParseMultipartFormFields aggregates repeated fields, prepareTranscriptionRequest now reads fallbacks, WriteMultipartField serializes arrays correctly), ensuring fallbacks are forwarded and attempted when primary providers fail.
Out of Scope Changes check ✅ Passed All changes directly support fixing the fallbacks bug: multipart parsing/writing enhancements enable proper handling of multiple fallback values, and prepareTranscriptionRequest integration ensures fallbacks flow through the request pipeline. No extraneous modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

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

Warning

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

🔧 golangci-lint (2.12.2)

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@mmelvin0 mmelvin0 changed the title fix: include fallbacks when preparing transcription requests (#4005) fix: include fallbacks when preparing transcription requests Jun 3, 2026
@mmelvin0
mmelvin0 force-pushed the transcription-fallbacks branch from 4f9b225 to 43029b7 Compare June 3, 2026 21:57
@mmelvin0
mmelvin0 marked this pull request as ready for review June 3, 2026 22:13
@greptile-apps

greptile-apps Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

The changes are narrowly scoped to transcription multipart handling and do not touch inference routing, pooled objects, or provider converters; the logic is straightforward and correct across all traced governance + multipart round-trip paths.

All three files change in a consistent, complementary way: the serialization layer, reconstruction layer, and handler all now agree on how multi-value form fields are represented. No new shared state, no concurrency surface, and no provider interface changes.

core/network/multipart_test.go — the new ParseMultipartFormFields aggregation path and ReconstructMultipartBody []string dedup path are not directly exercised by any test case.

Important Files Changed

Filename Overview
core/network/multipart.go Three connected fixes: ParseMultipartFormFields aggregates repeated field names into []string, WriteMultipartField writes []string as individual form fields instead of JSON, and ReconstructMultipartBody adds a writtenFields dedup guard to prevent []string values from being written once per original occurrence.
core/network/multipart_test.go Updates the WriteMultipartField test to assert []string return instead of JSON-encoded string; the new multi-value parsing behavior and ReconstructMultipartBody []string dedup path have no dedicated test coverage.
transports/bifrost-http/handlers/inference.go prepareTranscriptionRequest now reads form.Value["fallbacks"] and passes them through parseFallbacks into BifrostTranscriptionRequest.Fallbacks; also removes redundant schemas.ModelProvider() cast since provider is already that type.

Reviews (4): Last reviewed commit: "fix: include fallbacks when preparing tr..." | Re-trigger Greptile

Comment thread transports/bifrost-http/handlers/inference.go Outdated
@mmelvin0
mmelvin0 force-pushed the transcription-fallbacks branch from 43029b7 to f459b4c Compare June 3, 2026 23:25
@mmelvin0
mmelvin0 marked this pull request as draft June 4, 2026 02:43
@mmelvin0
mmelvin0 force-pushed the transcription-fallbacks branch from f459b4c to d0d91d4 Compare June 4, 2026 04:49
@mmelvin0

mmelvin0 commented Jun 4, 2026

Copy link
Copy Markdown
Author

I got rid of the JSON encoding of []string in multi part requests entirely.

It looks like nothing else was using this other than fallbacks and none of the inference handlers that read it were decoding the JSON anyway.

@mmelvin0
mmelvin0 marked this pull request as ready for review June 4, 2026 04:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/network/multipart_test.go (1)

223-241: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a regression test for repeated-field reconstruction (dedupe + order preservation).

This test covers write/parse, but the new ReconstructMultipartBody duplicate-skip logic is still untested for repeated keys (the critical fallback path). Please add a case that starts with repeated fallbacks, reconstructs with payload overrides, and asserts no duplicated entries and stable order.

As per coding guidelines, “Apply standard Go review practices: … deterministic tests, and table-driven coverage for behavior changes.”

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

In `@core/network/multipart_test.go` around lines 223 - 241, The test suite is
missing coverage for ReconstructMultipartBody's duplicate-skip fallback path for
repeated keys; add a deterministic test that writes an initial multipart body
containing repeated field "fallbacks" (e.g., ["f1","f2","f2","f3"]), then call
ReconstructMultipartBody with payload overrides that replace some entries, parse
the reconstructed body (using ParseMultipartFormFields) and assert that the
resulting "fallbacks" has no duplicated values and preserves the intended order
(stable ordering after dedupe), and include this as a new table-driven case
alongside the existing Test that exercises WriteMultipartField and
ParseMultipartFormFields so regressions in ReconstructMultipartBody are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@core/network/multipart_test.go`:
- Around line 223-241: The test suite is missing coverage for
ReconstructMultipartBody's duplicate-skip fallback path for repeated keys; add a
deterministic test that writes an initial multipart body containing repeated
field "fallbacks" (e.g., ["f1","f2","f2","f3"]), then call
ReconstructMultipartBody with payload overrides that replace some entries, parse
the reconstructed body (using ParseMultipartFormFields) and assert that the
resulting "fallbacks" has no duplicated values and preserves the intended order
(stable ordering after dedupe), and include this as a new table-driven case
alongside the existing Test that exercises WriteMultipartField and
ParseMultipartFormFields so regressions in ReconstructMultipartBody are caught.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0ffba43-c543-43ef-92c4-474d3f4672bd

📥 Commits

Reviewing files that changed from the base of the PR and between f459b4c and d0d91d4.

📒 Files selected for processing (3)
  • core/network/multipart.go
  • core/network/multipart_test.go
  • transports/bifrost-http/handlers/inference.go

impoiler and others added 17 commits June 4, 2026 15:28
…on rankings chart (maximhq#3950)

## Summary

Y-axis labels in the dimension rankings bar chart were taking up too much horizontal space and could overflow without truncation. This PR truncates long dimension names in the chart's Y-axis labels and adds a tooltip `<title>` element so users can still see the full name on hover.

## Changes

- Y-axis labels longer than 14 characters are now truncated with an ellipsis (`…`), with the full value exposed via an SVG `<title>` for accessibility and hover visibility
- Y-axis width reduced from `110` to `92` to match the shorter label space
- Left margin reduced from `4` to `0` to reclaim horizontal space

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

Navigate to the workspace dashboard and open the Dimension Rankings tab. Find a dimension with a long name (more than 14 characters) and verify:
1. The label is truncated with an ellipsis in the chart
2. Hovering over the label shows the full name via the browser's native tooltip

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

## Screenshots/Recordings

Add before/after screenshots showing the truncated Y-axis labels vs. the previous full-length labels.

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

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

* **Style**
  - Improved dimension ranking chart layout with optimized spacing
  - Long category labels now display with ellipsis truncation for better readability, with full text visible on hover
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
…le (maximhq#3952)

## Summary

Fixes layout issues in the `HeadersTable` component where columns were not properly constrained, causing inconsistent sizing of the Name, Value, and Actions columns.

## Changes

- Applied `table-fixed` layout to the table to enforce column width constraints
- Set the Name column to a fixed width of 40% to ensure consistent proportions between Name and Value columns
- Reduced the Actions column width from `w-12` to `w-10` and removed excess padding (`p-0`) to tighten the delete button column
- Removed padding from the Actions cell to better align the delete button within its column

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

Navigate to any view that renders the `HeadersTable` component (e.g., a request headers configuration panel) and verify:

1. The Name and Value columns maintain consistent proportions as rows are added or removed.
2. The Actions (delete) column remains compact and does not expand unexpectedly.
3. Long header names or values do not cause the table layout to shift.

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

## Screenshots/Recordings

Add before/after screenshots showing the corrected column widths in the headers table.

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

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

## Summary by CodeRabbit

* **Style**
  * Implemented fixed table layout to ensure consistent column widths and improved visual stability.
  * Refined header and action column sizing for better alignment and visual consistency.
  * Optimized spacing in the actions column for improved usability of row control buttons.

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

`StaleConnectionRetryIfErr` was not retrying on several real-world connection-closure errors because it relied on direct equality checks (`err == io.EOF`) and a fixed set of string patterns. Wrapped errors (e.g. `fmt.Errorf("read response: %w", io.EOF)`) were silently falling through without a retry, and error strings like `"use of closed network connection"` and `"server closed connection"` were not covered.

## Changes

- Replaced `err == io.EOF` with `errors.Is(err, io.EOF)` so wrapped EOF errors are correctly detected.
- Added `errors.Is(err, io.ErrUnexpectedEOF)` to handle unexpected EOF variants.
- Added string match patterns for `"use of closed network connection"` and `"server closed connection"` to cover additional OS- and fasthttp-level connection closure signals.
- Added an early-exit guard for `fasthttp.ErrConnectionClosed` to avoid retrying when fasthttp has already handled the error post-loop.
- Switched `err.Error()` to `strings.ToLower(err.Error())` for case-insensitive string matching consistency.
- Added corresponding test cases for wrapped `io.EOF`, `io.ErrUnexpectedEOF`, wrapped `io.ErrUnexpectedEOF`, `"use of closed network connection"`, and `"server closed connection"`.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./core/network/... -v -run TestStaleConnectionRetryIfErr
```

All existing and new test cases should pass, including the wrapped EOF and new connection-closure string variants.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None. This change only affects retry logic for stale HTTP connections and does not touch authentication, secrets, or PII handling.

## 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 is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Bug Fixes**
  * Improved HTTP client resilience by expanding retry behavior to handle a broader set of EOF and connection-closed scenarios.

* **Tests**
  * Expanded test coverage to validate additional EOF, wrapped-EOF, and server-closure cases.

* **Chores**
  * Minor formatting adjustment to an example configuration file.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary

Introduces a `source_of_truth` field to `config.json` that allows operators to make config.json sections authoritative during startup reconciliation. When set to `"config.json"`, any section explicitly present in the file becomes the single source of truth — database-only rows for that section are pruned. When omitted or set to `"split"` (the default), existing merge behavior is preserved.

## Changes

- Added `source_of_truth` field to `ConfigData` with two modes: `"split"` (default, existing behavior) and `"config.json"` (file-authoritative).
- Added `presentSections` and `presentGovernanceSections` tracking maps populated during `UnmarshalJSON` so that explicitly-present-but-empty sections (e.g., `"providers": {}`) can be distinguished from absent sections.
- Added `sectionPresent` and `governanceSectionPresent` helpers on `ConfigData` to query section presence.
- Introduced `syncAuthoritativeProvidersInStore` which, under `config.json` mode, deletes DB-only providers and DB-only keys within kept providers inside a single transaction.
- Introduced `processAuthoritativeProvider` as the authoritative counterpart to `processProvider`, preserving runtime-only fields (status, description) from the DB while making the file's key list canonical.
- Introduced `syncMCPConfigFromFile` which replaces stored MCP clients with exactly those declared in config.json, deleting DB-only clients and upserting file clients.
- Introduced `syncPluginsFromFile` which replaces stored plugins with exactly those declared in config.json, deleting DB-only plugins and upserting file plugins within a transaction.
- Introduced `pruneGovernanceConfigToFile` which, after the normal governance merge, removes DB-only rows for each governance collection that was explicitly present in the file (virtual keys, routing rules, pricing overrides, model configs, teams, customers, providers, budgets, rate limits).
- Added `source_of_truth` to `config.schema.json` as an enum of `["split", "config.json"]` with schema validation.
- Added a schema candidate path for tests running from `transports/bifrost-http/lib/`.
- Fixed `MockConfigStore.DeleteMCPClientConfig` and `DeletePlugin` to actually remove entries so sync tests can assert on store state.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./transports/bifrost-http/lib/... -run TestConfigDataSourceOfTruth
go test ./transports/bifrost-http/lib/... -run TestSourceOfTruthConfigJSON
go test ./transports/bifrost-http/lib/... -run TestSQLite_SourceOfTruthConfigJSON
go test ./transports/bifrost-http/lib/... -run TestConfigSchemaSourceOfTruthValidation
go test ./transports/bifrost-http/lib/...
```

**New `config.json` field:**

| Field | Type | Values | Default |
|---|---|---|---|
| `source_of_truth` | `string` | `"split"`, `"config.json"` | `"split"` |

To enable authoritative mode, add to `config.json`:
```json
{
  "source_of_truth": "config.json",
  "providers": { ... },
  "governance": { "budgets": [...] }
}
```

Only sections explicitly present in the file will be pruned in the database. Sections omitted from the file leave database rows untouched regardless of mode.

## Breaking changes

- [ ] Yes
- [x] No

Default behavior (`"split"`) is unchanged. Operators must explicitly opt in to `"config.json"` mode.

## Related issues

## Security considerations

Provider API keys that exist only in the database will be permanently deleted when `source_of_truth: "config.json"` is set and the `providers` section is present in the file. Operators should ensure all required keys are declared in config.json before enabling this mode.

## 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 PR closes a DNS rebinding vulnerability that existed between the time `ValidateExternalURL` validated a URL and the time the HTTP client actually opened a TCP connection. An attacker could exploit this window by having a hostname resolve to a public IP during validation and then to a private/internal IP at dial time, bypassing SSRF protections.

- Extracted `IsLocalhost` and `IsPrivateIP` into a new `core/network` package so they can be shared across validation and dialing layers.
- Updated `ConfigureDialer` to perform its own DNS resolution at dial time, reject any resolved private or loopback IPs, and dial the resolved IP literal directly — eliminating the TOCTOU window between URL validation and connection.
- Added `ValidateExternalURL` calls in the HTTP transport's `addProvider` and `updateProvider` handlers to reject private or loopback `BaseURL` values at the API boundary.
- Added `core/utils_test.go` with comprehensive tests covering `ValidateExternalURL`, `IsLocalhost`, and `IsPrivateIP`, including RFC 1918 ranges, link-local addresses, the AWS metadata endpoint (`169.254.169.254`), IPv6 private ranges, and query-parameter injection vectors.

- [x] Bug fix

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

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

Key scenarios validated by the test suite:
- `http://169.254.169.254/latest/meta-data/` → rejected as private IP
- `http://10.0.0.1/path?x=` → rejected as private IP
- `http://localhost:8080` → rejected as loopback
- `https://api.openai.com` → allowed
- IPv6 loopback (`::1`), link-local (`fe80::1`), and unique-local (`fc00::/7`) → all rejected

- [ ] Yes
- [x] No

This change directly addresses an SSRF / DNS rebinding attack vector. Previously, a malicious actor could register a hostname that resolved to a public IP during `ValidateExternalURL` and then switch the DNS record to an internal address (e.g., `169.254.169.254`, `10.x.x.x`) before the dialer connected. The dialer now resolves DNS independently, validates every returned IP against private ranges, and dials the IP literal, closing this window entirely. The `BaseURL` field on provider add/update endpoints is also now validated at the API 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)
- [ ] I verified the CI pipeline passes locally if applicable

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

* **Security Improvements**
  * Strengthened SSRF protections: centralized hostname/IP checks now block localhost, link-local, unspecified, and private IPs when validating external URLs and provider Base URLs.
  * Provider create/update endpoints validate Base URLs and return clear Bad Request responses for unsafe targets.
  * Network dialing now resolves hosts and rejects disallowed/private addresses early to prevent unsafe connections.

* **Tests**
  * Added extensive unit tests for URL validation, network address classification, and dialer SSRF behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary

Some OpenAI-compatible backends return valid SSE frames without a `Content-Type: text/event-stream` header. The previous `DrainNonSSEStreamResponse` helper would unconditionally drain and discard the response body in this case, causing the downstream SSE parser to receive an empty stream. This PR introduces a reader-preserving variant that peeks at the first bytes of the stream to detect SSE field prefixes (`data:`, `event:`, `id:`, `retry:`, `:`, or leading newlines) before deciding whether to drain or pass the reader through intact.

## Changes

- Introduced `DrainNonSSEStreamReader(resp, reader)` which accepts an `io.Reader` (e.g. a decompressed stream) and returns a potentially buffered reader alongside a `drained` boolean, preserving the stream when it looks like SSE even if the content type header is absent.
- `DrainNonSSEStreamResponse` is retained as a thin wrapper delegating to `DrainNonSSEStreamReader` for backward compatibility.
- All streaming handlers in the OpenAI provider (`text completion`, `chat completion`, `responses`, `speech`, `transcription`, `image generation`, `image edit`) now use `DrainNonSSEStreamReader` and reassign the reader from its return value so the buffered peek bytes are not lost.
- Added `looksLikeSSEPrefix` to perform a case-sensitive, 16-byte peek-based heuristic for SSE field prefixes.
- Added tests covering: SSE without content type remains readable, gzip-compressed SSE without content type remains readable, JSON without content type is drained, and uppercase SSE-like prefixes are treated as non-SSE.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./core/providers/utils/... -run TestDrainNonSSEStreamReader
go test ./...
```

Expected: all new `TestDrainNonSSEStreamReader_*` tests pass, and existing streaming tests remain green. To validate end-to-end, route a streaming request through a backend that returns SSE without `Content-Type: text/event-stream` and confirm the response is streamed correctly rather than returning an error.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None. The peek reads at most 16 bytes from the stream and does not log or expose any content.

## 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 is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Bug Fixes**
  * Improved streaming across text, chat, responses, speech, transcription, image generation and edit flows to detect SSE-like streams, avoid prematurely draining them, and prevent stream hangs or unexpected termination.

* **Tests**
  * Added and updated unit tests for SSE detection, compressed-stream handling, fragmented/tiny-prefix delivery, and correct draining behavior for non-SSE payloads.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary

Fixes a security vulnerability where provider API key headers injected by Bifrost upstream were being echoed back to clients in response headers. This was identified as a regression in the `/genai_passthrough` endpoint where `x-goog-api-key` values were leaking to clients (e.g., via Google's file-download 302 redirects).  
  
fixes maximhq#3954

## Changes

- Added `x-goog-api-key`, `x-api-key`, and `api-key` to the `providerResponseFilterHeaders` blocklist so they are stripped from upstream responses before being forwarded to clients.
- Added a regression test `TestExtractProviderResponseHeaders_StripsProviderSecrets` that verifies all four sensitive headers (`x-goog-api-key`, `x-api-key`, `api-key`, `authorization`) are stripped while benign headers like `x-request-id` are preserved.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./core/providers/utils/... -run TestExtractProviderResponseHeaders_StripsProviderSecrets -v
```

Expected output: the test passes, confirming that `x-goog-api-key`, `x-api-key`, `api-key`, and `authorization` are absent from the extracted response headers map, and that `x-request-id` is preserved.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Regression fix for `x-goog-api-key` leak via `/genai_passthrough`.

## Security considerations

This patch closes a credential leak where provider API keys (`x-goog-api-key`, `x-api-key`, `api-key`) injected into upstream requests could be reflected back to end clients in HTTP response headers. Any client receiving these responses prior to this fix may have been exposed to the upstream provider credentials. No new secrets or auth mechanisms are introduced.

## 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)
- [x] I verified the CI pipeline passes locally if applicable

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

* **Bug Fixes**
  * API key and authentication headers (case-insensitive) are now filtered from provider responses to prevent exposure of sensitive credentials to clients.
  * Legitimate non-sensitive response headers are preserved to maintain request tracing and debugging.

* **Tests**
  * Added a test that verifies sensitive provider headers are stripped while ensuring benign headers remain intact.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary

Passthrough requests previously had no usage extraction, meaning cost calculation and token logging were silently skipped for all provider passthrough endpoints. This PR adds per-provider usage extraction for both streaming and non-streaming passthrough responses across OpenAI, Azure, Anthropic, and Gemini, and wires the extracted usage into the pricing, logging, and governance plugins.

## Changes

- **New `BifrostPassthroughUsage` schema** added to `schemas/passthrough.go` carrying LLM tokens, image counts, audio chars/seconds, video seconds, and container identifiers — covering every billable endpoint type.
- **`PassthroughPath` field** added to `BifrostResponseExtraFields` and `BifrostPassthroughResponse` so the path is available downstream without re-parsing the original request.
- **Provider-level usage extractors** introduced as new files:
  - `core/providers/openai/passthrough_usage.go` — handles chat/completions, responses API, embeddings, speech (TTS), transcription/translation, image generation/edit/variation, video generation, and container creation.
  - `core/providers/anthropic/passthrough_usage.go` — handles `/messages` (SSE and non-streaming) and legacy `/complete`, including cache token details.
  - `core/providers/gemini/passthrough_usage.go` — handles `:generateContent`/`:streamGenerateContent` (text, audio, image output modalities), embeddings, Imagen (`:predict`), Veo (`:predictLongRunning`), and the Interactions API.
- **Streaming accumulation** updated across all four providers to accumulate the full response body (`accBody`) and call the usage extractor on the final EOF chunk, attaching `PassthroughUsage` to the terminal response.
- **`core/providers/utils/passthrough.go`** added with shared SSE parsing helpers (`ScanSSEDataLines`, `LastSSEDataLine`, `LastSSEOrBody`) used by all extractors.
- **Pricing integration** (`framework/modelcatalog/pricing.go`): `extractCostInput` now checks `PassthroughResponse.PassthroughUsage` first; `inferPassthroughRequestType` maps usage fields and path to the correct `RequestType`; `passthroughUsageToCostInput` converts the usage struct into the existing `costInput` shape so all existing compute functions apply without modification.
- **Logging plugin** (`plugins/logging/main.go`, `operations.go`): passthrough token usage is now applied to log entries via `applyNonStreamingOutputToEntry`, and streaming passthrough cost is computed in `PostLLMHook` when `PassthroughUsage` is present. The `Model` field is now forwarded in `PassthroughLogParams`.
- **Governance plugin** (`plugins/governance/main.go`): token usage is read from `PassthroughUsage.LLMUsage` for passthrough responses; `HasUsageData` now also triggers when `cost > 0` so non-token-based billing (images, audio, video) is tracked correctly.
- **`content-type` removed** from the provider response header filter list so it is forwarded to callers.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./core/... ./framework/... ./plugins/...
```

To validate end-to-end:
1. Send a passthrough request to `/v1/chat/completions`, `/v1/images/generations`, `/v1/audio/speech`, and a streaming `/v1/responses` endpoint via each supported provider.
2. Confirm that the log entry for each request contains a non-zero `cost` and populated `token_usage_parsed` (or the appropriate usage field for non-token endpoints).
3. For streaming passthrough, confirm that the final accumulated response includes `PassthroughUsage` and that cost appears in the governance usage tracker.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No new auth surfaces. The `content-type` header is now forwarded from providers to callers, which was previously suppressed — callers should be aware the response content type now reflects the provider's actual content type.

## Checklist

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

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

* **New Features**
  * Enhanced passthrough tracking: upstream request path is surfaced and detailed usage metrics (tokens, images, audio, video, container identifiers) are captured and returned for passthrough requests.
  * Streaming passthroughs now reliably forward raw chunks, observe incremental usage, and emit final usage on completion.

* **Improvements**
  * Pricing and logging now use passthrough usage to improve cost calculation and reporting.

* **Tests**
  * Added comprehensive tests for passthrough usage extraction and streaming across providers.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary

Enables CodeRabbit auto-reviews on all branches, not just the default branch.

## Changes

- Added `base_branches: [".*"]` to the CodeRabbit auto-review configuration so that pull requests targeting any branch are automatically reviewed, rather than only those targeting the default branch.

## Type of change

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

## Affected areas

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

## How to test

Open a pull request targeting a non-default branch and verify that CodeRabbit automatically triggers a review.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

None.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [ ] 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 is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Chores**
  * Updated automated review configuration to expand branch coverage.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
…o team and business unit attribute mappings (maximhq#3974)

## Summary

Adds optional `attributeType` and `attributeValue` fields to `attributeTeamMappings` and `attributeBusinessUnitMappings` to enable SCIM provisioning on a per-mapping basis. When these fields are present, a mapping can be matched against either a SCIM User attribute (`attributeType: "user"`) or a SCIM Group displayName (`attributeType: "group"`).

## Changes

- Added `attributeType` (enum: `"user"` | `"group"`) and `attributeValue` (string) as optional properties to `attributeTeamMappings` and `attributeBusinessUnitMappings` in both `helm-charts/bifrost/values.schema.json` and `transports/config.schema.json`.
- Added inline `description` fields to existing `attribute`, `value`, `team`, and `business_unit` properties for improved schema documentation.
- Added commented-out examples in `values.yaml` demonstrating SCIM provisioning via user attribute matching and group displayName matching.
- For `attributeType: "group"`, `attributeValue` is always expected to be `"displayName"` and is auto-set accordingly.

## Type of change

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

## Affected areas

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

## How to test

Configure `attributeTeamMappings` or `attributeBusinessUnitMappings` with the new fields and verify schema validation accepts valid inputs and rejects invalid ones (e.g., an `attributeType` value outside `["user", "group"]` or extra properties beyond those declared).

```sh
# Validate schema changes
go test ./...
```

Example mapping to validate:

```yaml
attributeTeamMappings:
  - attribute: "department"
    value: "engineering"
    team: "eng-team"
    attributeType: "user"
    attributeValue: "engineering"
  - attribute: "groups"
    value: "Engineering"
    team: "eng-team"
    attributeType: "group"
    attributeValue: "displayName"
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No new auth flows or secrets handling introduced. The new fields extend existing JWT claim-to-team/business-unit mapping logic with SCIM provisioning metadata only.

## Checklist

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

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

* **New Features**
  * Enhanced team and business-unit attribute mappings with two new optional fields to support SCIM provisioning metadata, enabling more flexible attribute-based provisioning.
* **Documentation**
  * Updated commented configuration examples to illustrate the new SCIM attribute/type/value mapping patterns.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary

When forwarding raw request bodies to Azure-hosted Anthropic models, the `diagnostics` field (used by Claude Code) is not supported by Azure's API and causes request failures. This PR strips the `diagnostics` field from raw request bodies when the target provider is Azure.

## Changes

- `StripUnsupportedFieldsFromRawBody` in `utils.go` now removes the `diagnostics` field from the raw JSON body when the provider is Azure.
- A new test case `azure_strips_claude_code_diagnostics` verifies that the `diagnostics` field is removed and that the model is correctly rewritten to the Azure deployment name when using raw request bodies.

## Type of change

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

## Affected areas

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

## How to test

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

The new test `azure_strips_claude_code_diagnostics` confirms that a raw request body containing a `diagnostics` field is sanitized before being sent to Azure, and that the Azure deployment name is correctly substituted as the model value.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No security implications. This change only removes a non-sensitive, provider-incompatible field from outbound requests.

## 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 is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Bug Fixes**
  * Azure Anthropic requests now strip an unsupported diagnostics field so deployments accept requests while preserving model selection.

* **New Features**
  * Diagnostics data is preserved for the Anthropic provider when the provider supports it.

* **Tests**
  * Added tests confirming diagnostics are kept for Anthropic and removed for other providers, and that Azure payloads have unsupported fields removed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary

When a virtual key has provider constraints (but no weights), the governance plugin now propagates the list of allowed providers into the Bifrost context. The router then intersects that list with the model catalog's provider list, ensuring that only providers permitted by the virtual key are considered for routing and fallbacks.

## Changes

- The governance plugin sets `BifrostContextKeyAvailableProviders` on the context after filtering provider configs, including setting an empty slice when no provider configs exist or when no providers pass the model filter.
- The router's `createHandler` intersects the catalog-derived provider list with any pre-existing `BifrostContextKeyAvailableProviders` value set by upstream plugins (e.g., governance). If the intersection is empty, an empty provider list is stored rather than falling back to the full catalog set.
- `extractAndParseFallbacks` now accepts a `BifrostContext` and filters parsed fallbacks to only those whose provider appears in `BifrostContextKeyAvailableProviders`. If all fallbacks are filtered out, the fallback list on the request is explicitly cleared to `nil`.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./plugins/governance/...
go test ./transports/bifrost-http/integrations/...
```

- A virtual key with `openai/gpt-4o` and `anthropic/claude-3-5-sonnet` provider configs (no weights) and a request for `gpt-4o` should result in `BifrostContextKeyAvailableProviders` containing only `openai`.
- A virtual key with only `openai/gpt-4o` and a request for `claude-3-5-sonnet` should result in an empty `BifrostContextKeyAvailableProviders`.
- A request with fallbacks that include providers not in the allowed list should have those fallbacks stripped before the request is dispatched.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes maximhq#2516

## Security considerations

Provider constraints enforced by virtual keys are now respected end-to-end through routing and fallback resolution, preventing requests from being routed to providers that the virtual key does not permit.

## 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 is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

## Release Notes

* **Tests**
  * Added comprehensive test coverage for governance HTTP transport pre-hook with provider-constrained virtual keys.
  * Added router tests verifying proper provider constraint enforcement during request handling.

* **Bug Fixes**
  * Router now correctly respects provider availability constraints when selecting providers for requests.
  * Fallback extraction now filters to only providers permitted by governance constraints.

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

## Summary

When a routing layer selects a specific provider, `CheckAndSetDefaultProvider` was ignoring that selection and falling back to the route's default provider. This PR ensures that a routing-resolved provider takes precedence over the default, as long as it is still within the allowed set of available providers.

## Changes

- Added a new context key `BifrostContextKeyResolvedProvider` to carry the provider selected by the routing layer.
- Updated `CheckAndSetDefaultProvider` to check for a resolved provider in context and return it immediately if it is present in the available providers list, before falling back to the default provider check.
- Added two tests: one verifying the resolved provider is used when allowed, and one verifying it is ignored when not in the available providers list.

## 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/providers/utils/... -run TestCheckAndSetDefaultProvider
```

Expected: both `TestCheckAndSetDefaultProviderUsesResolvedProvider` and `TestCheckAndSetDefaultProviderIgnoresDisallowedResolvedProvider` pass.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

The resolved provider context key is explicitly marked `DO NOT SET THIS MANUALLY` and is only populated by the routing layer. It cannot be used to bypass available-provider constraints, as the check enforces membership in the allowed list before honoring the resolved provider.

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

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

## Summary by CodeRabbit

## Release Notes

* **Improvements**
  * Enhanced provider selection logic to better utilize routing-determined providers when available.
  * Improved fallback behavior for provider resolution in routing scenarios.

* **Tests**
  * Added test coverage for provider selection scenarios with and without routing-resolved providers.

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

Adds support for `file` content blocks in chat message logs. Users can now view file metadata and download attached files directly from the log detail view.

## Changes

- Added a `file` content type and corresponding `file` field to the `ContentBlock` type in `logs.ts`, enabling the frontend to parse file blocks from message content
- Introduced `LogChatFileBlockView`, a new exported component that renders file block metadata (filename, type, size, file ID) and provides a download button when inline file data is available
- Integrated `LogChatFileBlockView` into `ContentBlockView` to handle `file`-typed content blocks in the general message view
- Rendered attached file blocks in `logDetailView.tsx` alongside image attachments when a message contains `file`-typed content
- Exposed `EnqueueLogEntry` as a public method on `LoggerPlugin` to allow external callers to push complete log entries through the plugin's async write queue

## Type of change

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

## Affected areas

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

## How to test

1. Send a request that includes a `file` content block in a chat message (e.g., using the Files API with an inline `file_data` payload or a `file_id` reference).
2. Open the log detail view for that request.
3. Verify the file block is rendered with its filename, type, size, and file ID.
4. If `file_data` is present, click **Download** and confirm the file downloads correctly.
5. If `file_url` is present, confirm the **Open file** link is rendered and navigates correctly.

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

## Screenshots/Recordings

_Add before/after screenshots showing the file block rendered in the log detail view._

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

Inline `file_data` is base64-encoded and decoded entirely in the browser before being offered as a download. No file data is sent to any external endpoint. Care should be taken to ensure that `file_data` in logs does not inadvertently expose sensitive content to unauthorized users viewing logs.

## Checklist

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

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

## Summary by CodeRabbit

* **New Features**
  * File content blocks are now rendered and viewable within message histories
  * Files attached to messages can be downloaded directly from log entries
  * File details including name, type, and size are displayed alongside message content

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

When replaying reasoning items through the OpenAI Responses API (e.g. Codex/GPT-5.5), the `content` field on a reasoning message can arrive as a string (notably an empty `""` after round-tripping). OpenAI types `reasoning.content` as an array of `reasoning_text` blocks and rejects a string value with `"expected an array ... got a string"`. This fix normalizes string content on outbound reasoning messages: empty strings are dropped entirely, and non-empty strings are promoted to a `reasoning_text` block.

## Changes

- In `ToOpenAIResponsesRequest`, when a reasoning message has `Content.ContentStr` set, the string is either dropped (if empty) or converted to a `ResponsesMessageContentBlock` with type `reasoning_text` (if non-empty). The reassignment operates on the local value copy to avoid mutating the caller's input.
- Tests cover both the empty-string drop case (including a marshal check to ensure `"content":""` never appears in the serialized output) and the non-empty string promotion case.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./core/providers/openai/... -run TestToOpenAIResponsesRequest_ReasoningStringContent -v
```

Expected: both subtests (`empty string content is dropped` and `non-empty string content becomes a reasoning_text block`) pass, and the marshalled output does not contain `"content":""` on any reasoning item.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

No auth, secrets, or PII implications. The fix only affects how reasoning message content is serialized before being sent to the OpenAI API.

## 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 is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

## Release Notes

* **Bug Fixes**
  * Fixed handling of reasoning message content when formatted as strings in OpenAI responses
  * Fixed JSON serialization of empty content fields to ensure API compliance

* **Chores**
  * Updated Go module dependencies across core, framework, and plugin packages for performance and security improvements

* **Tests**
  * Added test coverage for reasoning content normalization and empty content marshaling

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

OpenAI's Responses API rejects requests containing a `summary` field on compaction input items with an "Unknown parameter" error. Because Bifrost has no dedicated compaction item model, `encrypted_content` is carried via the embedded `*ResponsesReasoning` struct, which re-injects `"summary": null` during marshaling due to the absence of `omitempty`. This PR strips the `summary` field from compaction items post-serialization while leaving it intact on reasoning items, where it is required by OpenAI.

## Changes

- Added `ResponsesMessageTypeCompaction` constant to the `ResponsesMessageType` enum.
- Introduced `stripCompactionItemSummary`, which uses `sjson.DeleteBytes` to remove the `summary` key from any serialized item whose type is `compaction`.
- Wired `stripCompactionItemSummary` into both marshaling paths inside `OpenAIResponsesRequestInput.MarshalJSON` (the fast path and the `CacheControl` copy path).
- Added `github.com/tidwall/sjson` as a dependency for targeted JSON key deletion without full re-deserialization.
- Added `TestOpenAIResponsesRequest_MarshalJSON_CompactionSummaryStripped` to verify that compaction items have `summary` removed and `encrypted_content` retained, while sibling reasoning items keep their `summary` array.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./core/providers/openai/... -run TestOpenAIResponsesRequest_MarshalJSON_CompactionSummaryStripped -v
go test ./...
```

The new test asserts:
- Index 0 (compaction item): no `summary` key present, `encrypted_content` key present.
- Index 1 (reasoning item): `summary` key present with value `[]`.

## Breaking changes

- [x] No

## Security considerations

No auth, secrets, or PII implications. The change only affects JSON serialization of compaction items before they are sent to the OpenAI API.

## 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
* azure dignotstic property strip for claude models

* restrict fallbacks and provider selection to vk boundry

* capture resolved provider from the loadbalancer for logging

* logs adds support for rendering file attachments

* Update logChatMessageView.tsx

* openai integration content string handling

* handles compaction message type

* coderabbit yml changes

---------

Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
axelray-dev and others added 6 commits June 4, 2026 15:30
Add client_id, client_secret, tenant_id, and scopes to the
azure_key_config object in values.schema.json. These fields
exist in the Go AzureKeyConfig struct (core/schemas/account.go)
but were missing from the Helm chart schema, causing schema
validation to reject valid azure_key_config configurations.

Fixes maximhq#3990
…bility plugins with wildcard support (maximhq#4012)

## Summary

Observability plugins (OTel and Maxim) can now opt into capturing specific incoming request headers and attaching them to traces/spans as attributes or tags. Header capture is entirely opt-in and gated so there is zero overhead when no plugin requests it.

## Changes

- Added `MatchHeaderPattern` and `FilterHeaders` helpers to `core/schemas/headers.go` supporting exact names, trailing wildcards (`x-custom-*`), and the bare wildcard (`*`). The local duplicate in the logging plugin was removed in favour of the shared implementation.
- Added `RequestHeaders map[string]string` to `schemas.Trace`, with a thread-safe `SetRequestHeaders` setter and proper `Reset` clearing.
- Added `SetRequestHeaders` to `TraceStore` and three new methods to `Tracer`: `ShouldCaptureRequestHeaders`, `CollectRequestHeaderPatterns`, and `SetTraceRequestHeaders`. These derive live state from the stored plugins so no separate flag needs to be kept in sync.
- The HTTP tracing middleware now captures and lowercases all request headers onto the trace when `ShouldCaptureRequestHeaders` returns true, keeping the hot path free of allocation when no plugin opts in.
- The OTel plugin gains a per-profile `request_headers` config field. `RequestHeaderPatterns` satisfies the new interface the tracer checks. At conversion time, `convertTraceToResourceSpan` filters the trace's captured headers to the profile's own patterns and emits them as `http.request.header.<name>` span attributes on the root span.
- The Maxim plugin gains a `request_headers` config field. In `PostLLMHook`, matched headers are forwarded as `header.<name>` tags on both the generation and the trace.
- UI forms for both OTel and Maxim expose a textarea for entering comma-separated header patterns, with inline documentation warning that `*` will capture sensitive headers such as `Authorization`.

## Type of change

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

## Affected areas

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

## How to test

```sh
# Core/Transports
go test ./core/schemas/... ./framework/tracing/... ./plugins/otel/... ./plugins/maxim/... ./plugins/logging/...

# UI
cd ui
pnpm i
pnpm build
```

**OTel:** Add `"request_headers": ["x-tenant-id", "x-custom-*"]` to an OTel profile config, send a request with those headers, and verify the root span contains `http.request.header.x-tenant-id` (and any `x-custom-*` matches) as attributes.

**Maxim:** Add `"request_headers": ["x-tenant-id"]` to the Maxim config, send a request, and verify the generation and trace in Maxim carry a `header.x-tenant-id` tag.

**No-op path:** With no `request_headers` configured on any plugin, confirm `ShouldCaptureRequestHeaders` returns false and no header iteration occurs in the middleware.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

The `*` wildcard pattern will capture all headers, including `Authorization` and other credential-bearing headers. This is documented in the UI form descriptions and in code comments. Users should configure patterns as narrowly as possible. Captured headers are stored in-memory on the trace object and forwarded only to the observability backends the user has explicitly configured.

## Checklist

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

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

## Release Notes

**New Features**
- Added request header capture capability with pattern-based filtering (exact match and wildcard support)
- Maxim plugin now supports configuring which request headers to capture and attach as trace tags
- OTEL plugin now supports per-profile configuration for emitting request headers as span attributes
- UI forms now include configurable request header patterns for both Maxim and OTEL observability plugins
- ⚠️ Warning: Wildcard (`*`) pattern includes sensitive headers like Authorization
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* [docs] : google model armor as guardrail provider docs (maximhq#3660)

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

* fix: removes manual setting of type custom for anthropic tools (maximhq#3652)

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

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

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

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

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

- [ ] Yes
- [x] No

None.

- [ ] 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: fix ListModels for keyless providers (maximhq#3655)

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

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

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

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

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

- [ ] Yes
- [x] No

- Resolves maximhq#3607

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

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

* feat: preserve time filter params when navigating between sidebar items (maximhq#3647)

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

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

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

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

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

N/A

- [ ] Yes
- [x] No

No security implications. Only query parameters already present in the current URL are forwarded.

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

* refactor: move auth check from login component to route loader with redirect (maximhq#3648)

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

- [x] Refactor

- [x] UI (React)

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

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

- [ ] Yes
- [x] No

maximhq#3546

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

- [ ] 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: add team and bu filter support for dashboard and logs (maximhq#3650)

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

* fix: map vertex trrafic type to bifrost service tier (maximhq#3662)

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

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

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

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

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

None.

- [ ] 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: fixes the start time setting for correct ttft metric value (maximhq#3668)

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

- [x] Bug fix

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

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

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

- [x] No

None.

- [ ] 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: preserve tool call stop reason in Anthropic streaming fallback (maximhq#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (maximhq#3653)

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

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

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

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.

https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc

* [ ] Yes
* [x] No

BF-896

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] 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.

* table updates (maximhq#3665)

UI updates

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

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (maximhq#3560)" (maximhq#3669)

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

* chore: drop `access_profile_id` column from `governance_virtual_keys` (maximhq#3670)

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

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

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

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

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

N/A

- [ ] Yes
- [x] No

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

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

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (maximhq#3671)

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

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

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

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

- [ ] Yes
- [x] No

Covers migration test coverage for v1.5.3 schema additions.

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

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

* adds created_by for virtual keys (maximhq#3672)

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

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

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

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

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

N/A

- [ ] Yes
- [x] No

N/A

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

- [ ] 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: ensure toasts remain clickable above modal overlays (maximhq#3674)

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

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

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

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

- [ ] Yes
- [x] No

None.

- [ ] 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: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (maximhq#3675)

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

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

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

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

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

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

- [ ] Yes
- [x] No

None.

- [ ] 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: make virtual keys table fill available height with sticky header and scrollable body (maximhq#3676)

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

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

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

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

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

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

- [ ] Yes
- [x] No

None.

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

* v1.5.4 cut (maximhq#3680)

* fix idle timeout panic (maximhq#3677)

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

- [x] Bug fix

- [x] Core (Go)

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

- [ ] Yes
- [x] No

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

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

* mcp log fingerprinting (maximhq#3678)

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

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

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

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

N/A

- [ ] Yes
- [x] No

N/A

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] 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: update e2e ui tests (maximhq#3687)

Improves E2E test stability for virtual key editing by handling a budget reset dialog that can appear after saving, and marks a known flaky bulk-rotate test as `fixme` until the underlying UI bug is resolved.

- Added `preserveBudgetUsageIfPrompted()` helper that detects the `vk-budget-reset-dialog` and clicks the preserve button if it appears after saving a virtual key. This prevents test failures caused by an unexpected dialog interrupting the save flow.
- Marked `should bulk rotate selected virtual keys only` as `test.fixme` due to a UI bug where the checkbox selection state resets when the search input filters out a previously selected row. When `bulkRotateVirtualKeys` searches by name to select each key, the first key becomes deselected as the search narrows to the second, resulting in only the last key being rotated.

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

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

Run the virtual keys E2E suite and confirm no failures occur on the save flow due to the budget reset dialog:

```sh
cd tests/e2e
npx playwright test features/virtual-keys/virtual-keys.spec.ts
```

The bulk rotate test will be skipped (`fixme`) and should not cause CI failures.

N/A

- [ ] Yes
- [x] No

N/A

None.

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

* fix: updates mcp oauth api tests (maximhq#3693)

Consolidates the Per User OAuth Postman coverage probes to align with the updated API surface, replacing the old register/authorize/token/consent/upstream endpoints with the new flow-based endpoints (`/api/oauth/per-user/flows/:flowId` and `/api/oauth/per-user/flows/:flowId/start`).

- Removed coverage probe requests for `Per User OAuth Register`, `Per User OAuth Authorize`, `Per User OAuth Token`, `Per User OAuth Upstream Authorize`, `Per User Consent VK`, `Per User Consent User ID`, `Per User Consent Skip`, and `Per User Consent Submit`
- Added coverage probe requests for `Per User OAuth Flow Detail` (`GET /api/oauth/per-user/flows/coverage-probe-flow`) and `Per User OAuth Flow Start` (`GET /api/oauth/per-user/flows/coverage-probe-flow/start`)
- Added `OAuth Callback (Coverage Probe)` to the raw text shapes validation map, replacing the three previously tracked OAuth probe entries

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

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

Run the updated Postman collection against a running Bifrost instance and verify that the new flow-based coverage probe requests return expected responses and that the response structure validation passes.

- [ ] Yes
- [x] No

No security implications. These are test coverage probes only.

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

* [StepSecurity] Apply security best practices

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>

---------

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
Co-authored-by: Samyabrata Maji <samyabratamaji334@gmail.com>
Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com>
…fixes (maximhq#4024)

## Summary

Passthrough endpoints (`Passthrough` and `PassthroughStream`) were stripping `Content-Type` from provider response headers before forwarding them to callers. Since passthrough is meant to transparently relay provider responses, `Content-Type` must be preserved so clients can correctly interpret the response body.

## Changes

- Introduced `ExtractPassthroughProviderResponseHeaders`, a variant of `ExtractProviderResponseHeaders` that retains `Content-Type` while still filtering out transport-level headers (e.g., `content-encoding`, `transfer-encoding`, `connection`).
- Added `content-type` to the `providerResponseFilterHeaders` blocklist used by the standard `ExtractProviderResponseHeaders`, so non-passthrough paths continue to strip it.
- Switched all `Passthrough` and `PassthroughStream` implementations across Anthropic, Azure, Gemini, OpenAI, and Vertex providers to use the new `ExtractPassthroughProviderResponseHeaders`.
- Updated the `ErrConnectionClosed` retry test expectation to `wantRetry: true` to correctly reflect intended retry behavior.
- Replaced deprecated `gemini-2.0-flash` and `gemini-2.0-flash-lite` test harness entries with `gemini-2.5-flash` and `gemini-2.5-flash-lite`, and fixed a copy-paste error in the Azure embedding test path.

## Type of change

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

## Affected areas

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

## How to test

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

Send a passthrough request to any supported provider and verify that the `Content-Type` header (e.g., `application/json`) is present in the response forwarded by Bifrost.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No security implications. This change only affects which response headers are forwarded to the caller on passthrough paths.

## 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 is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Bug Fixes**
  * Passthrough responses now consistently preserve Content-Type and forward appropriate response headers across all AI providers, improving downstream compatibility.

* **Tests**
  * Updated test collections with latest Gemini/Vertex model identifiers and corrected Azure embeddings model path.
  * Added regression tests for passthrough header forwarding and adjusted connection-retry expectations.

<!-- review_stack_entry_start -->

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

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
…t at minimum sync interval (maximhq#4023)

## Summary

The sync worker ticker period was set to 1 hour, which created a subtle scheduling bug: when `pricingSyncInterval` is set near the minimum supported value, the few seconds a sync takes to complete causes the next ticker wake-up to land just under the elapsed-time threshold, effectively doubling the actual sync cadence. Reducing the ticker period to 5 minutes ensures the check granularity stays well below the minimum supported `pricingSyncInterval`, preventing ticker drift from defeating the threshold check.

## Changes

- Reduced `syncWorkerTickerPeriod` from 1 hour to 5 minutes so that ticker drift (caused by sync execution time) does not push the next wake-up just under the `pricingSyncInterval` threshold and inadvertently double the effective sync interval.
- Updated the scheduling model comment to accurately describe the relationship between the ticker period and `pricingSyncInterval`, removing the outdated note that implied the 1-hour ticker was a hard lower bound on sync frequency.

## 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 ./framework/modelcatalog/...
```

Set `pricingSyncInterval` to a value near `MinimumPricingSyncIntervalSec` and verify that syncs occur at the expected cadence without doubling.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

None.

## Checklist

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

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

## Summary by CodeRabbit

* **Bug Fixes**
  * Increased frequency of pricing synchronization checks from hourly intervals to 5-minute intervals, improving update timeliness and overall system reliability across different configurations.

* **Documentation**
  * Updated internal documentation clarifying the pricing synchronization scheduling mechanism and how timing parameters influence sync behavior.

<!-- review_stack_entry_start -->

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

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* fix(gemini): accept numeric schema integer constraints

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

* fix(gemini): address schema review feedback

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

* fix(gemini): simplify schema integer parsing
@akshaydeo
akshaydeo requested a review from a team as a code owner June 4, 2026 10:02
BearTS and others added 3 commits June 4, 2026 22:01
…nsaction support (maximhq#4039)

## Summary

`UpdateBudgetUsage` did not support transactional execution, making it impossible to include budget usage updates as part of a larger atomic database operation. This PR adds optional transaction support to bring it in line with other store methods.

## Changes

- Added an optional variadic `tx ...*gorm.DB` parameter to `UpdateBudgetUsage` in the `ConfigStore` interface, the `RDBConfigStore` implementation, and the `MockConfigStore` in tests.
- When a transaction is provided, the method uses it instead of the default DB connection.

## Type of change

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

## Affected areas

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

## How to test

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

## Screenshots/Recordings

N/A

## Breaking changes

- [x] Yes
- [ ] No

Any code calling `UpdateBudgetUsage` through the `ConfigStore` interface will need to be recompiled. The method signature has changed, though existing call sites that pass no transaction argument will continue to work without modification.

## Related issues

N/A

## Security considerations

No security implications. This change only affects how the database operation is scoped within a transaction.

## 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 is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Refactor**
  * Improved transaction handling in the configuration storage layer to support optional transactional updates for budget usage.
  * Aligned test mocks with the updated storage interface to ensure consistent behavior during testing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@mmelvin0
mmelvin0 force-pushed the transcription-fallbacks branch from d0d91d4 to 37e910f Compare June 4, 2026 16:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/network/multipart_test.go (1)

223-240: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add a regression test for duplicate-field reconstruction behavior.

Please add a focused case covering ReconstructMultipartBody’s new duplicate-skip path (Line 97–Line 100 in core/network/multipart.go) to assert payload-backed fields are written once even when the original multipart contains repeated field names.

As per coding guidelines, behavior changes in Go should include deterministic test coverage (preferably table-driven).

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

In `@core/network/multipart_test.go` around lines 223 - 240, Add a deterministic
unit test (table-driven style) in multipart_test.go that exercises
ReconstructMultipartBody’s duplicate-skip path by creating an original multipart
with repeated field names where one occurrence is payload-backed (e.g., a
file/part with payload metadata) and asserting that after calling
ReconstructMultipartBody the reconstructed body contains only a single written
instance of that payload-backed field (and other duplicate non-payload fields
are preserved or handled as expected); locate helper functions like
WriteMultipartField and ParseMultipartFormFields in the test to build the
original multipart, call ReconstructMultipartBody, and validate the parsed
output contains exactly one entry for the payload-backed field name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@core/network/multipart_test.go`:
- Around line 223-240: Add a deterministic unit test (table-driven style) in
multipart_test.go that exercises ReconstructMultipartBody’s duplicate-skip path
by creating an original multipart with repeated field names where one occurrence
is payload-backed (e.g., a file/part with payload metadata) and asserting that
after calling ReconstructMultipartBody the reconstructed body contains only a
single written instance of that payload-backed field (and other duplicate
non-payload fields are preserved or handled as expected); locate helper
functions like WriteMultipartField and ParseMultipartFormFields in the test to
build the original multipart, call ReconstructMultipartBody, and validate the
parsed output contains exactly one entry for the payload-backed field name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dc1b8bce-51e5-49a8-a8c1-cc15acd36d9f

📥 Commits

Reviewing files that changed from the base of the PR and between d0d91d4 and 37e910f.

📒 Files selected for processing (3)
  • core/network/multipart.go
  • core/network/multipart_test.go
  • transports/bifrost-http/handlers/inference.go
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/inference.go

@akshaydeo
akshaydeo force-pushed the dev branch 2 times, most recently from e389df7 to a65fce4 Compare June 8, 2026 11:25
@akshaydeo
akshaydeo force-pushed the dev branch 4 times, most recently from fa15f50 to ca190fc Compare June 21, 2026 11:44
@akshaydeo
akshaydeo force-pushed the dev branch 2 times, most recently from ac30a53 to 7c66b20 Compare July 1, 2026 12:24
@akshaydeo
akshaydeo force-pushed the dev branch 2 times, most recently from 44564de to 493bff0 Compare July 18, 2026 01:10
@akshaydeo

Copy link
Copy Markdown
Contributor

Hi @mmelvin0 — thanks for the contribution! This PR is currently blocked because our CLA bot shows the Contributor License Agreement as not yet signed. Could you sign it here so we can move this forward: https://cla-assistant.io/maximhq/bifrost?pullRequest=4011

Let us know if you run into any issues signing.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Simple transcription routing rule with fallback not working