Skip to content

fix: preserve non-pricing model pool entries across pricing reloads - #3999

Merged
akshaydeo merged 1 commit into
mainfrom
06-02-fix_repopulate_in_memory_model_pool_entry_to_avoid_losing_custom_models
Jun 2, 2026
Merged

akshaydeo merged 1 commit into
mainfrom
06-02-fix_repopulate_in_memory_model_pool_entry_to_avoid_losing_custom_models

Conversation

@Pratham-Mishra04

@Pratham-Mishra04 Pratham-Mishra04 commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

When populateModelPoolFromPricingData runs on a reload (e.g., gossip ReloadFromDB or ForceReloadPricing), it previously wiped modelPool and unfilteredModelPool entirely before rebuilding from pricing data. This caused models contributed by per-provider list-models output and allowed_models key entries to be silently dropped on every reload, creating drift between the initial state and the reloaded state.

Changes

  • Before wiping the model pools, a snapshot of the current modelPool and unfilteredModelPool is taken.
  • After the pricing-data rebuild completes, the snapshot is unioned back in, restoring any models that were added via UpsertModelDataForProvider / UpsertUnfilteredModelDataForProvider but are absent from the pricing sheet.
  • Pricing entries from the rebuild take precedence on duplicates (they are already written before the union step).
  • Models explicitly removed via DeleteModelDataForProvider are correctly excluded because that method strips the provider from the live map before this function runs.
  • baseModelIndex is intentionally not preserved across reloads, as aliases outside the pricing sheet have no canonical base-model entry and fall through to algorithmic stripping.

Type of change

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

Affected areas

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

How to test

Trigger a pricing reload (via gossip ReloadFromDB or ForceReloadPricing) on a running instance that has providers with list-models output or allowed_models entries. Verify that models contributed by those sources are still present in the model pool after the reload completes.

go test ./framework/modelcatalog/...

Breaking changes

  • Yes
  • No

Related issues

Security considerations

None.

Checklist

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

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where model data was being lost during pricing data reloads. Non-pricing model contributions are now preserved while pricing entries remain authoritative.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The populateModelPoolFromPricingData function in framework/modelcatalog/main.go now preserves non-pricing model contributions across pricing data rebuilds. A snapshot of existing model pools is captured before the rebuild clears them, then unioned back afterward, with pricing entries winning on conflicts.

Changes

Model Pool Preservation in Pricing Rebuild

Layer / File(s) Summary
Snapshot and union model pools
framework/modelcatalog/main.go
populateModelPoolFromPricingData snapshots modelPool and unfilteredModelPool before clearing them, then unions the snapshot back after rebuilding from pricing data, re-adding only models not already present per provider to ensure pricing entries take precedence while preserving non-pricing contributions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A snapshot, a wipe, then a union so neat,
Non-pricing pools dance through the pricing rebuild feat,
Pricing wins conflicts, but old friends stay—
Model pools bloom, both old and new way! 🌱

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: preserving non-pricing model pool entries across pricing reloads, which directly addresses the core bug being fixed.
Description check ✅ Passed The pull request description follows the template structure with all major sections completed: Summary, Changes, Type of change (Bug fix selected), Affected areas (Core Go selected), How to test provided, Breaking changes addressed, and Checklist items documented.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 06-02-fix_repopulate_in_memory_model_pool_entry_to_avoid_losing_custom_models

Warning

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

🔧 golangci-lint (2.12.2)

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


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

@CLAassistant

Copy link
Copy Markdown

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

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full-review

@greptile-apps

greptile-apps Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 3/5

The change fixes a real drift bug but introduces a mirrored staleness problem: models removed from the upstream pricing sheet will re-appear in the pool after every reload, remaining visible to callers until the next per-provider list-models refresh.

The snapshot-and-union approach correctly restores non-pricing models on reload, which is the stated goal. However, because the snapshot captures the entire model pool — including models whose only source was the previous pricing rebuild — any model deleted from the remote pricing sheet will be re-added by the union and will linger in the pool. This is a present defect on the changed path: after a reload that removes a model from pricing data, that model remains advertised as available. There are also no tests for the new reload behavior, leaving the fix invisible to the test suite.

framework/modelcatalog/main.go — the union loop and the absence of a dedicated reload test.

Important Files Changed

Filename Overview
framework/modelcatalog/main.go Adds a snapshot-and-union mechanism in populateModelPoolFromPricingData to preserve non-pricing model pool entries across reloads; the union inadvertently re-adds models that were legitimately removed from the remote pricing sheet, and no regression test is included.

Reviews (1): Last reviewed commit: "fix: repopulate in memory model pool ent..." | Re-trigger Greptile

Comment thread framework/modelcatalog/main.go
Comment thread framework/modelcatalog/main.go
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR details and diff
gh pr view 3999 --repo maximhq/bifrost

echo "=== PR DIFF ==="
gh pr diff 3999 --repo maximhq/bifrost

Length of output: 6093


🏁 Script executed:

#!/bin/bash
# Read the full modelcatalog main.go file
cat framework/modelcatalog/main.go

echo "=== TESTS ==="
fd -e go . framework/modelcatalog/ --exec cat {}

echo "=== CHECK IMPORTS ==="
head -50 framework/modelcatalog/main.go

Length of output: 274759



@Pratham-Mishra04 Here is a thorough review of the changes.


Review Summary

The fix is logically correct — the snapshot-then-union approach properly handles the three cases stated in the PR description (list-models additions, allowed_models aliases, and DeleteModelDataForProvider removals). A few items worth addressing before merge:


1. 🔴 No regression test for the fixed behaviour

The PR checklist acknowledges this, but a test is important here because the bug is purely about state surviving a function call. Without it, a future refactor of populateModelPoolFromPricingData could silently reintroduce the drift.

A minimal test skeleton, given the existing newTestCatalog / noOpLogger helpers in the package:

func TestPopulateModelPool_PreservesNonPricingEntriesOnReload(t *testing.T) {
    mc := newTestCatalog(nil, nil)
    mc.logger = noOpLogger{}

    // Simulate a non-pricing model added via UpsertModelDataForProvider
    mc.mu.Lock()
    mc.modelPool[schemas.OpenAI] = []string{"gpt-4o", "custom-model"}
    mc.unfilteredModelPool[schemas.OpenAI] = []string{"gpt-4o", "custom-model"}
    mc.mu.Unlock()

    // Add a pricing entry only for gpt-4o
    mc.pricingData[makeKey("gpt-4o", "openai", "chat")] = configstoreTables.TableModelPricing{
        Model: "gpt-4o", Provider: "openai", Mode: "chat",
    }

    // Simulate a reload
    mc.populateModelPoolFromPricingData()

    models := mc.GetModelsForProvider(schemas.OpenAI)
    assert.Contains(t, models, "gpt-4o")
    assert.Contains(t, models, "custom-model", "non-pricing model must survive reload")

    unfiltered := mc.GetUnfilteredModelsForProvider(schemas.OpenAI)
    assert.Contains(t, unfiltered, "custom-model", "non-pricing model must survive reload in unfiltered pool")
}

func TestPopulateModelPool_DeletedProviderNotRestoredOnReload(t *testing.T) {
    mc := newTestCatalog(nil, nil)
    mc.logger = noOpLogger{}

    mc.mu.Lock()
    mc.modelPool[schemas.OpenAI] = []string{"gpt-4o"}
    mc.unfilteredModelPool[schemas.OpenAI] = []string{"gpt-4o"}
    mc.mu.Unlock()

    // Simulate DeleteModelDataForProvider being called before the reload
    mc.DeleteModelDataForProvider(schemas.OpenAI)

    mc.populateModelPoolFromPricingData()

    // Provider was explicitly deleted — must not be restored from snapshot
    models := mc.GetModelsForProvider(schemas.OpenAI)
    assert.Empty(t, models, "explicitly deleted provider must not be restored on reload")
}

2. 🟡 O(n²) union due to slices.Contains

The union loop is:

for provider, models := range previousModelPool {
    for _, m := range models {
        if !slices.Contains(mc.modelPool[provider], m) {  // O(n) scan per model
            mc.modelPool[provider] = append(mc.modelPool[provider], m)
        }
    }
}

For providers like OpenRouter or Vertex that can carry thousands of models, this becomes O(m × n) per provider. Consider building a set from the already-rebuilt slice once per provider:

for provider, models := range previousModelPool {
    existing := make(map[string]struct{}, len(mc.modelPool[provider]))
    for _, m := range mc.modelPool[provider] {
        existing[m] = struct{}{}
    }
    for _, m := range models {
        if _, ok := existing[m]; !ok {
            mc.modelPool[provider] = append(mc.modelPool[provider], m)
        }
    }
}
// same pattern for previousUnfilteredModelPool

This reduces the inner check to O(1) and the whole pass to O(m + n).


3. 🟢 Shared slice between modelPool and unfilteredModelPool (minor)

After the pricing rebuild:

mc.modelPool[provider] = models
mc.unfilteredModelPool[provider] = models   // same backing array

Go's append will create separate backing arrays when capacity is exceeded, so the union step's independent appends are safe in practice. However, a reader who isn't familiar with Go slice semantics might find this surprising. A defensive copy (append([]string(nil), models...) or slices.Clone(models)) on one assignment would make the intent explicit and eliminate the dependency on append-on-overflow behaviour.


4. 🟢 baseModelIndex gap acknowledged — worth a comment in UpsertModelDataForProvider

Models restored from the snapshot won't have baseModelIndex entries, so getBaseModelNameUnsafe falls back to algorithmic stripping for them. This is correctly documented in the populateModelPoolFromPricingData comment. It may be worth a brief inline comment near the UpsertModelDataForProvider call sites noting that callers should not rely on catalog-based base-model resolution for models that are not in the pricing sheet, to help future contributors understand the constraint.


Overall: The bug fix is sound. Addressing the missing test (item 1) and the O(n²) union (item 2) would make this production-ready. Items 3 and 4 are minor polish.

Copy link
Copy Markdown
Contributor

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
framework/modelcatalog/main.go (1)

409-494: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add regression tests for snapshot-union model pool rebuild behavior
No framework/modelcatalog tests exercise populateModelPoolFromPricingData’s snapshot-then-union logic (existing tests focus on base model/name/capabilities and pricing computations; the only UpsertModelDataForProvider usage found is in a governance prehook test and doesn’t cover rebuild/persistence/deletion). Add tests covering:

  • non-pricing models preserved across the rebuild (via UpsertModelDataForProvider / UpsertUnfilteredModelDataForProvider)
  • pricing models present after rebuild
  • models removed via DeleteModelDataForProvider remain excluded after rebuild
🤖 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 `@framework/modelcatalog/main.go` around lines 409 - 494, Add regression tests
that exercise ModelCatalog.populateModelPoolFromPricingData to verify
snapshot-then-union behavior: create a ModelCatalog test instance, seed
mc.pricingData with a few pricing entries, then call UpsertModelDataForProvider
and UpsertUnfilteredModelDataForProvider to add non-pricing models and also call
DeleteModelDataForProvider to remove one provider/model; invoke
populateModelPoolFromPricingData (or the public flow that triggers it) and
assert three things: (1) pricing models from mc.pricingData are present in
mc.modelPool/mc.unfilteredModelPool, (2) non-pricing models added via
UpsertModelDataForProvider / UpsertUnfilteredModelDataForProvider are preserved
after the rebuild, and (3) any models removed via DeleteModelDataForProvider
remain absent after the rebuild; use the concrete function names
(populateModelPoolFromPricingData, UpsertModelDataForProvider,
UpsertUnfilteredModelDataForProvider, DeleteModelDataForProvider) to locate code
and add table-driven tests covering multiple providers and mix of
duplicate/unique models.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/modelcatalog/main.go`:
- Line 463: The assignment mc.unfilteredModelPool[provider] = models shares the
slice backing array with mc.modelPool[provider]; change the assignment to create
a defensive copy (e.g., copy into a new slice using append([]Model(nil),
models...) or make+copy) when setting both mc.modelPool[provider] and
mc.unfilteredModelPool[provider] so each map entry has its own backing array and
future appends to one won't affect the other; update the code around where
models is assigned into mc.modelPool and mc.unfilteredModelPool (referencing
mc.modelPool, mc.unfilteredModelPool, provider, models) to use the copy
approach.
- Around line 472-485: The current union loops iterate
previousModelPool/previousUnfilteredModelPool and call slices.Contains against
mc.modelPool/mc.unfilteredModelPool which makes the merge O(m×n); instead, for
each provider build a lookup set (map[T]struct{}) of existing entries from
mc.modelPool[provider] (and separately for mc.unfilteredModelPool[provider])
then iterate the snapshot models and append only if the lookup misses; update
the loops that reference slices.Contains to use these per-provider maps to
reduce complexity to O(m+n) per provider.

---

Outside diff comments:
In `@framework/modelcatalog/main.go`:
- Around line 409-494: Add regression tests that exercise
ModelCatalog.populateModelPoolFromPricingData to verify snapshot-then-union
behavior: create a ModelCatalog test instance, seed mc.pricingData with a few
pricing entries, then call UpsertModelDataForProvider and
UpsertUnfilteredModelDataForProvider to add non-pricing models and also call
DeleteModelDataForProvider to remove one provider/model; invoke
populateModelPoolFromPricingData (or the public flow that triggers it) and
assert three things: (1) pricing models from mc.pricingData are present in
mc.modelPool/mc.unfilteredModelPool, (2) non-pricing models added via
UpsertModelDataForProvider / UpsertUnfilteredModelDataForProvider are preserved
after the rebuild, and (3) any models removed via DeleteModelDataForProvider
remain absent after the rebuild; use the concrete function names
(populateModelPoolFromPricingData, UpsertModelDataForProvider,
UpsertUnfilteredModelDataForProvider, DeleteModelDataForProvider) to locate code
and add table-driven tests covering multiple providers and mix of
duplicate/unique models.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d980c285-64d5-4faf-819b-8f2ee0750507

📥 Commits

Reviewing files that changed from the base of the PR and between a70594c and 658136d.

📒 Files selected for processing (1)
  • framework/modelcatalog/main.go

Comment thread framework/modelcatalog/main.go
Comment thread framework/modelcatalog/main.go

akshaydeo commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • Jun 2, 5:50 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 2, 5:50 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo merged commit 934d393 into main Jun 2, 2026
14 of 15 checks passed
@akshaydeo
akshaydeo deleted the 06-02-fix_repopulate_in_memory_model_pool_entry_to_avoid_losing_custom_models branch June 2, 2026 17:50
@akshaydeo akshaydeo mentioned this pull request Jun 4, 2026
18 tasks
akshaydeo added a commit that referenced this pull request Jun 4, 2026
## Summary

Releases core v1.5.16, framework v1.3.16, transports v1.5.8, and bumps all plugins to pick up the new core and framework versions. This release adds `file://` scheme support for pricing URLs, paginated virtual key fetching, and fixes several correctness issues across Bedrock, OpenAI→Anthropic conversion, MCP stdio, and streaming responses.

## Changes

- **File scheme pricing URLs** — Pricing source URLs now accept `file://`, enabling local filesystem pricing data for air-gapped and self-hosted deployments (#4045)
- **Paginated virtual key fetch** — Virtual key retrieval is now paginated to avoid loading all keys into memory at once for large deployments (#3957)
- **Preserve model pool entries on pricing reload** — Non-pricing model pool entries are no longer dropped when pricing data is reloaded (#3999)
- **Bedrock `outputAssessments` type** — Corrected the type of `outputAssessments` in Bedrock response structs (#4028)
- **`Model` field in `TextCompletionChunkResponse`** — Added the missing `Model` field to text completion chunk responses (#3970)
- **Orphaned tool results in OpenAI→Anthropic conversion** — Orphaned tool results no longer cause rejections from the Anthropic API (#3919)
- **MCP inline stdio env assignments** — MCP stdio server configs now correctly parse inline environment variable assignments (#3861)

## Type of change

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

## Affected areas

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

## How to test

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

- To test `file://` pricing URLs, configure a pricing URL using the `file:///path/to/pricing.json` scheme and verify pricing data loads correctly from the local file.
- To test paginated virtual key fetch, create a large number of virtual keys and confirm they are all returned correctly without memory issues.
- To test orphaned tool results, send a request through the OpenAI→Anthropic conversion flow containing a tool result with no matching tool call and verify it is accepted rather than rejected.
- To test MCP inline stdio env, configure an MCP stdio server with inline env var assignments (e.g. `KEY=value`) and verify the server starts correctly.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes #4045, #3957, #3999, #4028, #3970, #3919, #3861

## Security considerations

No new security implications. This release does not touch auth, secrets handling, or sandboxing.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
@akshaydeo akshaydeo mentioned this pull request Jun 5, 2026
18 tasks
akshaydeo added a commit that referenced this pull request Jun 6, 2026
## Summary

This PR bumps the Go toolchain version from `1.26.3` to `1.26.4` across all modules and CI workflows, and cuts a new release (`core` v1.5.17, `framework` v1.3.17, `transports` v1.5.9, `plugins/compat` v0.1.16, `plugins/governance` v1.5.17, and associated plugin versions) incorporating a large batch of features and fixes accumulated since the previous release.

## Changes

- **Go 1.26.4** — Updated `go-version` in all GitHub Actions workflows (`e2e-tests`, `helm-release`, `pr-tests`, `release-cli`, `release-pipeline`, `snyk`) and all `go.mod` files (core, framework, transports, cli, all plugins, examples, and test modules).
- **Core (v1.5.17)** — OpenAI compaction support, multi-customer logs and usage tracking, multiple team/business unit support, `request_headers` wildcard pattern capture for OTel and Maxim plugins, xAI `x_search` tool, fetch URL validation with SSRF hardening, `file://` pricing URL scheme, virtual key provider fan-out filtering, and a broad set of fixes including Anthropic prompt cache key, empty thinking block stripping, OpenAI stream usage event cleanup, Gemini numeric schema constraints, stale connection retries, Azure Claude diagnostic strip, and passthrough budget handling.
- **Framework (v1.3.17)** — Scope-aware budgets and limits wired from model configs, provider-level governance, multiple customer budget support with `calendar_aligned` windows, paginated virtual key fetch, `config.json` source-of-truth flow, FTS index cap reduction, sync worker drift fix, cascade deletes for model configs, and high-scale virtual key flow improvements.
- **Transports (v1.5.9)** — Full changelog covering all of the above plus UI improvements (log navigation, customer detail sheet, `BudgetDisplay` component, inline loading shell, materialized view alias), SCIM provisioning fields, Helm/config schema additions (`roles`, `per_user_oauth`), client IP resolution from forwarded headers, and dependency upgrades (`recharts` to 3.8.1, `golang.org/x` CVE remediation).
- **Plugins** — `governance` v1.5.17 adds team budget/rate-limit exporters, ghost node reconciliation fix, and VK double usage counting fix; `logging` v1.5.17 adds wildcard header capture and file attachment rendering; `otel` v1.2.17 adds `disable_content_logging` and multiple collectors support; `maxim` v1.6.17 adds `request_headers` wildcard capture; `compat` v0.1.16 fixes `max_tokens` preservation during param filtering.

## Type of change

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

## Affected areas

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

## How to test

```sh
# Verify Go version
go version  # should report go1.26.4

# Run core tests
cd core && go test ./...

# Run framework tests
cd framework && go test ./...

# Run transports tests
cd transports && go test ./...

# Run plugin tests
cd plugins/governance && go test ./...
cd plugins/logging && go test ./...
cd plugins/otel && go test ./...

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

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

#4053, #4066, #4041, #4012, #3976, #3947, #3991, #4045, #3957, #3938, #3937, #3939, #3981, #3998, #3997, #4092, #4091, #4079, #4080, #4086, #3929, #3994, #4028, #3970, #3919, #3861, #3664, #3999, #4088, #4070, #4051, #4043, #4057, #4023, #3941, #3955, #4024, #3956, #3967, #3925, #3992, #3900

## Security considerations

- Fetch URL validation hardened against SSRF by tightening IP checks for private networks and link-local addresses (#4092, #3947, #3991).
- Transitive `golang.org/x` dependencies (crypto, net, sys, text) bumped to address Docker Scout CVEs (#3900).

## Checklist

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

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

* **New Features**
  * OpenAI compaction, multi-customer/team logstore support, request-header wildcard capture, enhanced governance (provider-level & scope-aware limits), disable-content-logging option, support for multiple OpenTelemetry collectors, SSRF hardening and URL validation.

* **Chores**
  * Bumped Go toolchain across modules and updated component/plugin version releases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@akshaydeo akshaydeo mentioned this pull request Jun 7, 2026
akshaydeo added a commit that referenced this pull request Jun 7, 2026
## ✨ Features

- **OpenAI Compaction** — Added OpenAI conversation compaction support
across core, framework, logging, and the API surface (#4053)
- **Multi-Customer & Org Hierarchy** — Logs and usage tracking now
support multiple customers, teams, and business units, including
business unit CRUD, team assignment, and governance endpoints in the
OpenAPI spec (#4066, #4041, #4082)
- **Provider-Level Governance** — Budgets & limits are now scope-aware
and can be applied at the virtual-key top level and per provider, wired
from the model configs table, with UI filters for scope and providers
(#3938, #3937, #3939, #3981, #3962)
- **Customer Budgets** — Customers support multiple budgets and
`calendar_aligned` budget windows (#3998, #3997)
- **Virtual Key Attribution & Controls** — Added a `created_by` user
attribution column and a `blacklisted_models` column for virtual key
provider configs (#3672, #3653)
- **Request Header Capture** — OTel and Maxim observability plugins
capture `request_headers` by pattern, with wildcard support (e.g.
`x-custom-*`); logging gained the same wildcard header capture (#4012,
#3958)
- **OTel Content Controls & Collectors** — New `disable_content_logging`
option drops message/tool content from exported spans, plus support for
multiple OTel collectors (#4064, #3894)
- **xAI x_search** — Added xAI `x_search` tool support (#3976)
- **URL Validation** — Added fetch URL validation with private-network
configuration and link-local blocking (#3947, #3991)
- **File Scheme Pricing URLs** — Pricing source URLs now accept the
`file://` scheme for air-gapped and self-hosted deployments (#4045)
- **Paginated Virtual Keys** — Virtual key fetching is paginated to
handle deployments with very large numbers of keys (#3957)
- **Client IP Resolution** — Resolve client IP from
`X-Forwarded-For`/`X-Real-IP` headers
- **SCIM Provisioning** — Added `attributeType`/`attributeValue` SCIM
provisioning fields
- **Helm/Config Schema** — Added `roles` RBAC governance config and
`per_user_oauth` MCP auth to the Helm chart and config schema (#4004,
#4009)
- **Log Navigation UI** — Added a "View logs" menu item to customer,
team, and virtual key tables, clickable links in log detail views, a
customer detail sheet, and a reusable `BudgetDisplay` component (#4073,
#4054, #4026, #4055)
- **Faster First Paint** — Added an inline loading shell to `#root`
before React mounts (#4063)
- **Materialized View Alias** — Added an `alias` column to the
materialized view with filter support (#4078)

## 🐞 Fixed

- **Fetch URL IP Checks** — Hardened fetch URL IP checks against SSRF
(#4092)
- **Mantle Model Matching** — Broadened Mantle model matching to all
`gpt` variants (#4091)
- **Empty Thinking Blocks** — Strip thinking blocks when the signature
is empty (#4079)
- **OpenAI Stream Usage** — Removed usage from the `responses.created`
event in the OpenAI stream (#4080)
- **Prompt Cache Key** — Set the prompt cache key from the Anthropic
integration (#4086)
- **Upstream Failure Status** — Map upstream connection failures to 502
instead of 400 (#3929) (thanks
[@chris-colinsky](https://github.com/chris-colinsky)!)
- **Gemini Schema Constraints** — Accept numeric schema integer
constraints for Gemini (#3994) (thanks
[@yanhao98](https://github.com/yanhao98)!)
- **Files Provider Param** — Accept the `?provider=` query param on `GET
/v1/files` (#3971) (thanks [@alexef](https://github.com/alexef)!)
- **Optional Batch Model** — Made the `model` field optional on `POST
/v1/batches` (#3973) (thanks [@alexef](https://github.com/alexef)!)
- **Helm Azure Config** — Added missing `azure_key_config` fields to the
Helm schema (#3996) (thanks
[@axelray-dev](https://github.com/axelray-dev)!)
- **Text Completion Chunk Model** — Added the missing `Model` field to
`TextCompletionChunkResponse` (#3970) (thanks
[@kuishou68](https://github.com/kuishou68)!)
- **MCP Inline stdio Env** — MCP stdio server configs accept inline
environment variable assignments (#3861) (thanks
[@Shushmitaaaa](https://github.com/Shushmitaaaa)!)
- **Orphaned Tool Results** — Orphaned tool results in the OpenAI to
Anthropic conversion flow are no longer rejected by the Anthropic API
(#3919)
- **Node Usage Reconciliation** — Added a monotonic `inc_number` log
cursor so node usage reconciliation does not skip late async log writes
(#3664)
- **Bedrock Output Assessments** — Corrected the type of
`outputAssessments` in Bedrock responses (#4028)
- **Model Pool Pricing Reloads** — Preserve non-pricing model pool
entries across pricing reloads (#3999)
- **Ghost Node Reconciliation** — Replicate the VK hierarchy flow for
ghost node reconciliation (#4088)
- **VK Double Usage Counting** — Fixed double usage counting when
creating a virtual key (#4070)
- **Model Config Lifecycle** — Cascade deletes for model configs and
removal of stale in-memory model configs (#4051, #4043)
- **FTS Index Cap** — Reduced the FTS index `left()` cap from 800k to
250k chars to stay within the tsvector limit (#4057)
- **Sync Worker Drift** — Reduced the sync worker ticker period to 5m to
prevent threshold drift (#4023)
- **Passthrough** — Fixed passthrough budgets, gated passthrough models
per VK, model extraction for Azure passthrough, and restricted
fallbacks/provider selection to the VK boundary (#3941, #3988, #3983,
#3924)
- **Provider Response Headers** — Strip provider response headers and
add a content-type filter (#3955, #4024)
- **Stream Handling** — Drain non-SSE stream readers and retry stale
connections (#3956, #3967)
- **Azure Claude** — Strip Azure diagnostic property for Claude models
(#3925)
- **Compat max_tokens** — Preserve chat `max_tokens` during param
filtering (#3992)
- **Raw Request Flag** — Removed the raw request flag from providers
that don't support it (#4058)
- **UI Fixes** — Standardized page container layout, virtual key model
configs UI, and dashboard chart tooltips (#4046, #4052, #4044)

## 🔧 Maintenance

- **Dependency Upgrades** — Bumped transitive `golang.org/x`
dependencies (crypto, net, sys, text) for Docker Scout CVE remediation
and `recharts` to 3.8.1; cascaded version bumps across all modules
(#3900, #4003)
akhsaul pushed a commit to akhsaul/bifrost that referenced this pull request Aug 27, 2026
…aximhq#3999)

## Summary

When `populateModelPoolFromPricingData` runs on a reload (e.g., gossip `ReloadFromDB` or `ForceReloadPricing`), it previously wiped `modelPool` and `unfilteredModelPool` entirely before rebuilding from pricing data. This caused models contributed by per-provider list-models output and `allowed_models` key entries to be silently dropped on every reload, creating drift between the initial state and the reloaded state.

## Changes

- Before wiping the model pools, a snapshot of the current `modelPool` and `unfilteredModelPool` is taken.
- After the pricing-data rebuild completes, the snapshot is unioned back in, restoring any models that were added via `UpsertModelDataForProvider` / `UpsertUnfilteredModelDataForProvider` but are absent from the pricing sheet.
- Pricing entries from the rebuild take precedence on duplicates (they are already written before the union step).
- Models explicitly removed via `DeleteModelDataForProvider` are correctly excluded because that method strips the provider from the live map before this function runs.
- `baseModelIndex` is intentionally not preserved across reloads, as aliases outside the pricing sheet have no canonical base-model entry and fall through to algorithmic stripping.

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

Trigger a pricing reload (via gossip `ReloadFromDB` or `ForceReloadPricing`) on a running instance that has providers with list-models output or `allowed_models` entries. Verify that models contributed by those sources are still present in the model pool after the reload completes.

```sh
go test ./framework/modelcatalog/...
```

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

* **Bug Fixes**
  * Fixed an issue where model data was being lost during pricing data reloads. Non-pricing model contributions are now preserved while pricing entries remain authoritative.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
akhsaul pushed a commit to akhsaul/bifrost that referenced this pull request Aug 27, 2026
## Summary

Releases core v1.5.16, framework v1.3.16, transports v1.5.8, and bumps all plugins to pick up the new core and framework versions. This release adds `file://` scheme support for pricing URLs, paginated virtual key fetching, and fixes several correctness issues across Bedrock, OpenAI→Anthropic conversion, MCP stdio, and streaming responses.

## Changes

- **File scheme pricing URLs** — Pricing source URLs now accept `file://`, enabling local filesystem pricing data for air-gapped and self-hosted deployments (maximhq#4045)
- **Paginated virtual key fetch** — Virtual key retrieval is now paginated to avoid loading all keys into memory at once for large deployments (maximhq#3957)
- **Preserve model pool entries on pricing reload** — Non-pricing model pool entries are no longer dropped when pricing data is reloaded (maximhq#3999)
- **Bedrock `outputAssessments` type** — Corrected the type of `outputAssessments` in Bedrock response structs (maximhq#4028)
- **`Model` field in `TextCompletionChunkResponse`** — Added the missing `Model` field to text completion chunk responses (maximhq#3970)
- **Orphaned tool results in OpenAI→Anthropic conversion** — Orphaned tool results no longer cause rejections from the Anthropic API (maximhq#3919)
- **MCP inline stdio env assignments** — MCP stdio server configs now correctly parse inline environment variable assignments (maximhq#3861)

## Type of change

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

## Affected areas

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

## How to test

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

- To test `file://` pricing URLs, configure a pricing URL using the `file:///path/to/pricing.json` scheme and verify pricing data loads correctly from the local file.
- To test paginated virtual key fetch, create a large number of virtual keys and confirm they are all returned correctly without memory issues.
- To test orphaned tool results, send a request through the OpenAI→Anthropic conversion flow containing a tool result with no matching tool call and verify it is accepted rather than rejected.
- To test MCP inline stdio env, configure an MCP stdio server with inline env var assignments (e.g. `KEY=value`) and verify the server starts correctly.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes maximhq#4045, maximhq#3957, maximhq#3999, maximhq#4028, maximhq#3970, maximhq#3919, maximhq#3861

## Security considerations

No new security implications. This release does not touch auth, secrets handling, or sandboxing.

## 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
akhsaul pushed a commit to akhsaul/bifrost that referenced this pull request Aug 27, 2026
## ✨ Features

- **OpenAI Compaction** — Added OpenAI conversation compaction support
across core, framework, logging, and the API surface (maximhq#4053)
- **Multi-Customer & Org Hierarchy** — Logs and usage tracking now
support multiple customers, teams, and business units, including
business unit CRUD, team assignment, and governance endpoints in the
OpenAPI spec (maximhq#4066, maximhq#4041, maximhq#4082)
- **Provider-Level Governance** — Budgets & limits are now scope-aware
and can be applied at the virtual-key top level and per provider, wired
from the model configs table, with UI filters for scope and providers
(maximhq#3938, maximhq#3937, maximhq#3939, maximhq#3981, maximhq#3962)
- **Customer Budgets** — Customers support multiple budgets and
`calendar_aligned` budget windows (maximhq#3998, maximhq#3997)
- **Virtual Key Attribution & Controls** — Added a `created_by` user
attribution column and a `blacklisted_models` column for virtual key
provider configs (maximhq#3672, maximhq#3653)
- **Request Header Capture** — OTel and Maxim observability plugins
capture `request_headers` by pattern, with wildcard support (e.g.
`x-custom-*`); logging gained the same wildcard header capture (maximhq#4012,
maximhq#3958)
- **OTel Content Controls & Collectors** — New `disable_content_logging`
option drops message/tool content from exported spans, plus support for
multiple OTel collectors (maximhq#4064, maximhq#3894)
- **xAI x_search** — Added xAI `x_search` tool support (maximhq#3976)
- **URL Validation** — Added fetch URL validation with private-network
configuration and link-local blocking (maximhq#3947, maximhq#3991)
- **File Scheme Pricing URLs** — Pricing source URLs now accept the
`file://` scheme for air-gapped and self-hosted deployments (maximhq#4045)
- **Paginated Virtual Keys** — Virtual key fetching is paginated to
handle deployments with very large numbers of keys (maximhq#3957)
- **Client IP Resolution** — Resolve client IP from
`X-Forwarded-For`/`X-Real-IP` headers
- **SCIM Provisioning** — Added `attributeType`/`attributeValue` SCIM
provisioning fields
- **Helm/Config Schema** — Added `roles` RBAC governance config and
`per_user_oauth` MCP auth to the Helm chart and config schema (maximhq#4004,
maximhq#4009)
- **Log Navigation UI** — Added a "View logs" menu item to customer,
team, and virtual key tables, clickable links in log detail views, a
customer detail sheet, and a reusable `BudgetDisplay` component (maximhq#4073,
maximhq#4054, maximhq#4026, maximhq#4055)
- **Faster First Paint** — Added an inline loading shell to `#root`
before React mounts (maximhq#4063)
- **Materialized View Alias** — Added an `alias` column to the
materialized view with filter support (maximhq#4078)

## 🐞 Fixed

- **Fetch URL IP Checks** — Hardened fetch URL IP checks against SSRF
(maximhq#4092)
- **Mantle Model Matching** — Broadened Mantle model matching to all
`gpt` variants (maximhq#4091)
- **Empty Thinking Blocks** — Strip thinking blocks when the signature
is empty (maximhq#4079)
- **OpenAI Stream Usage** — Removed usage from the `responses.created`
event in the OpenAI stream (maximhq#4080)
- **Prompt Cache Key** — Set the prompt cache key from the Anthropic
integration (maximhq#4086)
- **Upstream Failure Status** — Map upstream connection failures to 502
instead of 400 (maximhq#3929) (thanks
[@chris-colinsky](https://github.com/chris-colinsky)!)
- **Gemini Schema Constraints** — Accept numeric schema integer
constraints for Gemini (maximhq#3994) (thanks
[@yanhao98](https://github.com/yanhao98)!)
- **Files Provider Param** — Accept the `?provider=` query param on `GET
/v1/files` (maximhq#3971) (thanks [@alexef](https://github.com/alexef)!)
- **Optional Batch Model** — Made the `model` field optional on `POST
/v1/batches` (maximhq#3973) (thanks [@alexef](https://github.com/alexef)!)
- **Helm Azure Config** — Added missing `azure_key_config` fields to the
Helm schema (maximhq#3996) (thanks
[@axelray-dev](https://github.com/axelray-dev)!)
- **Text Completion Chunk Model** — Added the missing `Model` field to
`TextCompletionChunkResponse` (maximhq#3970) (thanks
[@kuishou68](https://github.com/kuishou68)!)
- **MCP Inline stdio Env** — MCP stdio server configs accept inline
environment variable assignments (maximhq#3861) (thanks
[@Shushmitaaaa](https://github.com/Shushmitaaaa)!)
- **Orphaned Tool Results** — Orphaned tool results in the OpenAI to
Anthropic conversion flow are no longer rejected by the Anthropic API
(maximhq#3919)
- **Node Usage Reconciliation** — Added a monotonic `inc_number` log
cursor so node usage reconciliation does not skip late async log writes
(maximhq#3664)
- **Bedrock Output Assessments** — Corrected the type of
`outputAssessments` in Bedrock responses (maximhq#4028)
- **Model Pool Pricing Reloads** — Preserve non-pricing model pool
entries across pricing reloads (maximhq#3999)
- **Ghost Node Reconciliation** — Replicate the VK hierarchy flow for
ghost node reconciliation (maximhq#4088)
- **VK Double Usage Counting** — Fixed double usage counting when
creating a virtual key (maximhq#4070)
- **Model Config Lifecycle** — Cascade deletes for model configs and
removal of stale in-memory model configs (maximhq#4051, maximhq#4043)
- **FTS Index Cap** — Reduced the FTS index `left()` cap from 800k to
250k chars to stay within the tsvector limit (maximhq#4057)
- **Sync Worker Drift** — Reduced the sync worker ticker period to 5m to
prevent threshold drift (maximhq#4023)
- **Passthrough** — Fixed passthrough budgets, gated passthrough models
per VK, model extraction for Azure passthrough, and restricted
fallbacks/provider selection to the VK boundary (maximhq#3941, maximhq#3988, maximhq#3983,
maximhq#3924)
- **Provider Response Headers** — Strip provider response headers and
add a content-type filter (maximhq#3955, maximhq#4024)
- **Stream Handling** — Drain non-SSE stream readers and retry stale
connections (maximhq#3956, maximhq#3967)
- **Azure Claude** — Strip Azure diagnostic property for Claude models
(maximhq#3925)
- **Compat max_tokens** — Preserve chat `max_tokens` during param
filtering (maximhq#3992)
- **Raw Request Flag** — Removed the raw request flag from providers
that don't support it (maximhq#4058)
- **UI Fixes** — Standardized page container layout, virtual key model
configs UI, and dashboard chart tooltips (maximhq#4046, maximhq#4052, maximhq#4044)

## 🔧 Maintenance

- **Dependency Upgrades** — Bumped transitive `golang.org/x`
dependencies (crypto, net, sys, text) for Docker Scout CVE remediation
and `recharts` to 3.8.1; cascaded version bumps across all modules
(maximhq#3900, maximhq#4003)
occcat pushed a commit to occcat/bifrost that referenced this pull request Sep 2, 2026
…aximhq#3999)

## Summary

When `populateModelPoolFromPricingData` runs on a reload (e.g., gossip `ReloadFromDB` or `ForceReloadPricing`), it previously wiped `modelPool` and `unfilteredModelPool` entirely before rebuilding from pricing data. This caused models contributed by per-provider list-models output and `allowed_models` key entries to be silently dropped on every reload, creating drift between the initial state and the reloaded state.

## Changes

- Before wiping the model pools, a snapshot of the current `modelPool` and `unfilteredModelPool` is taken.
- After the pricing-data rebuild completes, the snapshot is unioned back in, restoring any models that were added via `UpsertModelDataForProvider` / `UpsertUnfilteredModelDataForProvider` but are absent from the pricing sheet.
- Pricing entries from the rebuild take precedence on duplicates (they are already written before the union step).
- Models explicitly removed via `DeleteModelDataForProvider` are correctly excluded because that method strips the provider from the live map before this function runs.
- `baseModelIndex` is intentionally not preserved across reloads, as aliases outside the pricing sheet have no canonical base-model entry and fall through to algorithmic stripping.

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

Trigger a pricing reload (via gossip `ReloadFromDB` or `ForceReloadPricing`) on a running instance that has providers with list-models output or `allowed_models` entries. Verify that models contributed by those sources are still present in the model pool after the reload completes.

```sh
go test ./framework/modelcatalog/...
```

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

* **Bug Fixes**
  * Fixed an issue where model data was being lost during pricing data reloads. Non-pricing model contributions are now preserved while pricing entries remain authoritative.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
occcat pushed a commit to occcat/bifrost that referenced this pull request Sep 2, 2026
## Summary

Releases core v1.5.16, framework v1.3.16, transports v1.5.8, and bumps all plugins to pick up the new core and framework versions. This release adds `file://` scheme support for pricing URLs, paginated virtual key fetching, and fixes several correctness issues across Bedrock, OpenAI→Anthropic conversion, MCP stdio, and streaming responses.

## Changes

- **File scheme pricing URLs** — Pricing source URLs now accept `file://`, enabling local filesystem pricing data for air-gapped and self-hosted deployments (maximhq#4045)
- **Paginated virtual key fetch** — Virtual key retrieval is now paginated to avoid loading all keys into memory at once for large deployments (maximhq#3957)
- **Preserve model pool entries on pricing reload** — Non-pricing model pool entries are no longer dropped when pricing data is reloaded (maximhq#3999)
- **Bedrock `outputAssessments` type** — Corrected the type of `outputAssessments` in Bedrock response structs (maximhq#4028)
- **`Model` field in `TextCompletionChunkResponse`** — Added the missing `Model` field to text completion chunk responses (maximhq#3970)
- **Orphaned tool results in OpenAI→Anthropic conversion** — Orphaned tool results no longer cause rejections from the Anthropic API (maximhq#3919)
- **MCP inline stdio env assignments** — MCP stdio server configs now correctly parse inline environment variable assignments (maximhq#3861)

## Type of change

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

## Affected areas

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

## How to test

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

- To test `file://` pricing URLs, configure a pricing URL using the `file:///path/to/pricing.json` scheme and verify pricing data loads correctly from the local file.
- To test paginated virtual key fetch, create a large number of virtual keys and confirm they are all returned correctly without memory issues.
- To test orphaned tool results, send a request through the OpenAI→Anthropic conversion flow containing a tool result with no matching tool call and verify it is accepted rather than rejected.
- To test MCP inline stdio env, configure an MCP stdio server with inline env var assignments (e.g. `KEY=value`) and verify the server starts correctly.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Closes maximhq#4045, maximhq#3957, maximhq#3999, maximhq#4028, maximhq#3970, maximhq#3919, maximhq#3861

## Security considerations

No new security implications. This release does not touch auth, secrets handling, or sandboxing.

## 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
occcat pushed a commit to occcat/bifrost that referenced this pull request Sep 2, 2026
## ✨ Features

- **OpenAI Compaction** — Added OpenAI conversation compaction support
across core, framework, logging, and the API surface (maximhq#4053)
- **Multi-Customer & Org Hierarchy** — Logs and usage tracking now
support multiple customers, teams, and business units, including
business unit CRUD, team assignment, and governance endpoints in the
OpenAPI spec (maximhq#4066, maximhq#4041, maximhq#4082)
- **Provider-Level Governance** — Budgets & limits are now scope-aware
and can be applied at the virtual-key top level and per provider, wired
from the model configs table, with UI filters for scope and providers
(maximhq#3938, maximhq#3937, maximhq#3939, maximhq#3981, maximhq#3962)
- **Customer Budgets** — Customers support multiple budgets and
`calendar_aligned` budget windows (maximhq#3998, maximhq#3997)
- **Virtual Key Attribution & Controls** — Added a `created_by` user
attribution column and a `blacklisted_models` column for virtual key
provider configs (maximhq#3672, maximhq#3653)
- **Request Header Capture** — OTel and Maxim observability plugins
capture `request_headers` by pattern, with wildcard support (e.g.
`x-custom-*`); logging gained the same wildcard header capture (maximhq#4012,
maximhq#3958)
- **OTel Content Controls & Collectors** — New `disable_content_logging`
option drops message/tool content from exported spans, plus support for
multiple OTel collectors (maximhq#4064, maximhq#3894)
- **xAI x_search** — Added xAI `x_search` tool support (maximhq#3976)
- **URL Validation** — Added fetch URL validation with private-network
configuration and link-local blocking (maximhq#3947, maximhq#3991)
- **File Scheme Pricing URLs** — Pricing source URLs now accept the
`file://` scheme for air-gapped and self-hosted deployments (maximhq#4045)
- **Paginated Virtual Keys** — Virtual key fetching is paginated to
handle deployments with very large numbers of keys (maximhq#3957)
- **Client IP Resolution** — Resolve client IP from
`X-Forwarded-For`/`X-Real-IP` headers
- **SCIM Provisioning** — Added `attributeType`/`attributeValue` SCIM
provisioning fields
- **Helm/Config Schema** — Added `roles` RBAC governance config and
`per_user_oauth` MCP auth to the Helm chart and config schema (maximhq#4004,
maximhq#4009)
- **Log Navigation UI** — Added a "View logs" menu item to customer,
team, and virtual key tables, clickable links in log detail views, a
customer detail sheet, and a reusable `BudgetDisplay` component (maximhq#4073,
maximhq#4054, maximhq#4026, maximhq#4055)
- **Faster First Paint** — Added an inline loading shell to `#root`
before React mounts (maximhq#4063)
- **Materialized View Alias** — Added an `alias` column to the
materialized view with filter support (maximhq#4078)

## 🐞 Fixed

- **Fetch URL IP Checks** — Hardened fetch URL IP checks against SSRF
(maximhq#4092)
- **Mantle Model Matching** — Broadened Mantle model matching to all
`gpt` variants (maximhq#4091)
- **Empty Thinking Blocks** — Strip thinking blocks when the signature
is empty (maximhq#4079)
- **OpenAI Stream Usage** — Removed usage from the `responses.created`
event in the OpenAI stream (maximhq#4080)
- **Prompt Cache Key** — Set the prompt cache key from the Anthropic
integration (maximhq#4086)
- **Upstream Failure Status** — Map upstream connection failures to 502
instead of 400 (maximhq#3929) (thanks
[@chris-colinsky](https://github.com/chris-colinsky)!)
- **Gemini Schema Constraints** — Accept numeric schema integer
constraints for Gemini (maximhq#3994) (thanks
[@yanhao98](https://github.com/yanhao98)!)
- **Files Provider Param** — Accept the `?provider=` query param on `GET
/v1/files` (maximhq#3971) (thanks [@alexef](https://github.com/alexef)!)
- **Optional Batch Model** — Made the `model` field optional on `POST
/v1/batches` (maximhq#3973) (thanks [@alexef](https://github.com/alexef)!)
- **Helm Azure Config** — Added missing `azure_key_config` fields to the
Helm schema (maximhq#3996) (thanks
[@axelray-dev](https://github.com/axelray-dev)!)
- **Text Completion Chunk Model** — Added the missing `Model` field to
`TextCompletionChunkResponse` (maximhq#3970) (thanks
[@kuishou68](https://github.com/kuishou68)!)
- **MCP Inline stdio Env** — MCP stdio server configs accept inline
environment variable assignments (maximhq#3861) (thanks
[@Shushmitaaaa](https://github.com/Shushmitaaaa)!)
- **Orphaned Tool Results** — Orphaned tool results in the OpenAI to
Anthropic conversion flow are no longer rejected by the Anthropic API
(maximhq#3919)
- **Node Usage Reconciliation** — Added a monotonic `inc_number` log
cursor so node usage reconciliation does not skip late async log writes
(maximhq#3664)
- **Bedrock Output Assessments** — Corrected the type of
`outputAssessments` in Bedrock responses (maximhq#4028)
- **Model Pool Pricing Reloads** — Preserve non-pricing model pool
entries across pricing reloads (maximhq#3999)
- **Ghost Node Reconciliation** — Replicate the VK hierarchy flow for
ghost node reconciliation (maximhq#4088)
- **VK Double Usage Counting** — Fixed double usage counting when
creating a virtual key (maximhq#4070)
- **Model Config Lifecycle** — Cascade deletes for model configs and
removal of stale in-memory model configs (maximhq#4051, maximhq#4043)
- **FTS Index Cap** — Reduced the FTS index `left()` cap from 800k to
250k chars to stay within the tsvector limit (maximhq#4057)
- **Sync Worker Drift** — Reduced the sync worker ticker period to 5m to
prevent threshold drift (maximhq#4023)
- **Passthrough** — Fixed passthrough budgets, gated passthrough models
per VK, model extraction for Azure passthrough, and restricted
fallbacks/provider selection to the VK boundary (maximhq#3941, maximhq#3988, maximhq#3983,
maximhq#3924)
- **Provider Response Headers** — Strip provider response headers and
add a content-type filter (maximhq#3955, maximhq#4024)
- **Stream Handling** — Drain non-SSE stream readers and retry stale
connections (maximhq#3956, maximhq#3967)
- **Azure Claude** — Strip Azure diagnostic property for Claude models
(maximhq#3925)
- **Compat max_tokens** — Preserve chat `max_tokens` during param
filtering (maximhq#3992)
- **Raw Request Flag** — Removed the raw request flag from providers
that don't support it (maximhq#4058)
- **UI Fixes** — Standardized page container layout, virtual key model
configs UI, and dashboard chart tooltips (maximhq#4046, maximhq#4052, maximhq#4044)

## 🔧 Maintenance

- **Dependency Upgrades** — Bumped transitive `golang.org/x`
dependencies (crypto, net, sys, text) for Docker Scout CVE remediation
and `recharts` to 3.8.1; cascaded version bumps across all modules
(maximhq#3900, maximhq#4003)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants