Skip to content

feat(providers): add MiniMax provider - #4922

Closed
octo-patch wants to merge 86 commits into
maximhq:devfrom
octo-patch:octo/minimax-provider
Closed

feat(providers): add MiniMax provider#4922
octo-patch wants to merge 86 commits into
maximhq:devfrom
octo-patch:octo/minimax-provider

Conversation

@octo-patch

@octo-patch octo-patch commented Jul 5, 2026

Copy link
Copy Markdown

Summary

Adds MiniMax as a first-class Bifrost provider with current model, endpoint, schema, UI, and documentation coverage.

Changes

  • Registers MiniMax across core construction, configuration schemas, migration handling, UI metadata, and API documentation.
  • Supports model listing, chat completions, streaming chat completions, and the native Responses API.
  • Honors context path overrides for streaming requests and forwards MiniMax-specific request parameters.
  • Preserves MiniMax image and video content fields through the shared chat schema and generated OpenAPI definitions.
  • Covers MiniMax-M3 and MiniMax-M2.7, including multimodal input, thinking controls, context windows, regional endpoints, and tiered pricing.
  • Returns an unsupported-operation error for classic text completions because the upstream API does not expose that route.
  • Adds a local SSE regression test for path overrides, request parameter forwarding, and multimodal content forwarding.

Type of change

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

Affected areas

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

How to test

cd core
env MINIMAX_API_KEY= GOWORK=off go test -count=1 ./providers/minimax
env MINIMAX_API_KEY= GOWORK=off go vet ./providers/minimax
env MINIMAX_API_KEY= GOWORK=off go test -run '^$' .

cd ..
env MINIMAX_API_KEY= PATH="$(go env GOPATH)/bin:$PATH" make test-core PROVIDER=minimax PATTERN=ContextPath

cd docs/openapi
python bundle.py

cd ../../ui
npm run build-enterprise
npm run typecheck

OpenAPI generation, Helm schema validation, the UI enterprise build, formatting, typecheck, and documentation parsing pass on the final tree. The MiniMax regression test, provider vet, and core compile check pass after locally removing the current dev branch's stale rawToolSearch assignment; the final tree inherits that pre-existing compile error and intentionally does not include the unrelated workaround. The live integration suite skips when no MiniMax API key is supplied.

Screenshots/Recordings

Not applicable. The UI change registers provider metadata and the MiniMax brand icon.

Breaking changes

  • Yes
  • No

Related issues

N/A

Security considerations

The API key remains stored through the existing secret variable mechanism and is sent only in the authorization header. No key values are logged or included in tests or documentation.

Checklist

  • I read the repository contribution guidelines and followed them
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the applicable CI checks locally

@CLAassistant

CLAassistant commented Jul 5, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds MiniMax as a supported provider with OpenAI-compatible chat and Responses APIs, streaming support, provider registration, configuration, tests, documentation, media schemas, and UI metadata.

Changes

MiniMax provider integration

Layer / File(s) Summary
Provider contracts and media schemas
core/schemas/..., transports/..., docs/openapi/..., helm-charts/...
Registers MiniMax across provider allowlists and adds video input and pricing-related schema fields.
MiniMax provider implementation
core/providers/minimax/...
Adds provider construction, model listing, chat and Responses APIs, streaming handlers, cached-content stubs, and unsupported-operation responses.
Provider construction and test configuration
core/bifrost.go, core/internal/llmtests/..., core/providers/minimax/*_test.go
Wires provider creation and configures keys, network settings, expectations, comprehensive scenarios, custom paths, and forwarded parameters.
Documentation and UI metadata
docs/providers/..., docs/docs.json, ui/lib/constants/..., core/changelog.md, transports/changelog.md
Adds MiniMax documentation, navigation and support-matrix entries, labels, placeholders, key requirements, icon metadata, and changelog entries.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: akshaydeo, danpiths, pratham-mishra04, roroghost17, r-droid101

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding MiniMax provider support.
Description check ✅ Passed The description follows the template well and includes summary, changes, test steps, impact, security, and checklist items.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

Safe to merge; the new provider follows established patterns with no changes to existing provider paths.

The MiniMax provider delegates entirely to the existing OpenAI-compatible helpers. Streaming uses the dedicated streaming client built by BuildStreamingClient. All unsupported operations return the standard unsupported-operation error. The shared schema additions (video_url, max_long_side_pixel) are purely additive with omitempty and do not alter serialization for any existing provider. No mutations to shared mutable state, no new goroutines, no fasthttp acquire/release concerns beyond what the existing helpers already manage.

No files require special attention; the most cross-cutting change is core/schemas/chatcompletions.go (shared schema extensions), which is backward-compatible.

Important Files Changed

Filename Overview
core/providers/minimax/minimax.go New MiniMax provider using OpenAI-compatible helpers; correctly uses BuildStreamingClient for streaming paths, BifrostContextKeyPassthroughExtraParams for extra params, bearer auth, and unsupported-operation errors for unimplemented operations.
core/providers/minimax/minimax_unit_test.go Local SSE regression test verifying context path override, extra-params forwarding, and multimodal (image + video) content block passthrough; test provider reuses the same fasthttp.Client for both client and streamingClient but this doesn't affect correctness for the scenarios covered.
core/schemas/chatcompletions.go Adds ChatContentBlockTypeVideo constant, ChatInputVideo struct, and MaxLongSidePixel field to ChatInputImage; all additions use omitempty so they are backward-compatible for all providers that don't populate these fields.
core/schemas/bifrost.go Adds Minimax ModelProvider constant and adds it to StandardProviders (correctly omitted from SupportedBaseProviders, which is reserved for custom-provider base types).
transports/config.schema.json Adds minimax as a recognized provider key in all three relevant locations (provider definitions, provider enum, and key provider enum), consistent with other providers.
ui/lib/constants/icons.tsx Adds inline SVG MiniMax brand icon with a linear gradient; uses a fixed gradient ID (minimax-icon-gradient) consistent with the existing pattern for other icons in the same file.
docs/openapi/schemas/inference/chat.yaml Adds video_url to ChatContentBlock enum and ChatInputVideo schema; extends ChatInputImage.detail enum with the default value used by MiniMax and adds max_long_side_pixel field.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant Bifrost
    participant MinimaxProvider
    participant OpenAIHelper
    participant MiniMaxAPI

    Caller->>Bifrost: ChatCompletion / ChatCompletionStream
    Bifrost->>MinimaxProvider: route to provider
    MinimaxProvider->>MinimaxProvider: SetValue(PassthroughExtraParams, true)
    alt Unary
        MinimaxProvider->>OpenAIHelper: HandleOpenAIChatCompletionRequest(client, ...)
        OpenAIHelper->>MiniMaxAPI: POST /v1/chat/completions
        MiniMaxAPI-->>OpenAIHelper: JSON response
        OpenAIHelper-->>MinimaxProvider: BifrostChatResponse
        MinimaxProvider-->>Bifrost: BifrostChatResponse
    else Streaming
        MinimaxProvider->>OpenAIHelper: HandleOpenAIChatCompletionStreaming(streamingClient, ...)
        OpenAIHelper->>MiniMaxAPI: POST /v1/chat/completions (SSE)
        MiniMaxAPI-->>OpenAIHelper: SSE chunks
        OpenAIHelper-->>MinimaxProvider: chan BifrostStreamChunk
        MinimaxProvider-->>Bifrost: chan BifrostStreamChunk
    end
    Bifrost-->>Caller: response / stream
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant Bifrost
    participant MinimaxProvider
    participant OpenAIHelper
    participant MiniMaxAPI

    Caller->>Bifrost: ChatCompletion / ChatCompletionStream
    Bifrost->>MinimaxProvider: route to provider
    MinimaxProvider->>MinimaxProvider: SetValue(PassthroughExtraParams, true)
    alt Unary
        MinimaxProvider->>OpenAIHelper: HandleOpenAIChatCompletionRequest(client, ...)
        OpenAIHelper->>MiniMaxAPI: POST /v1/chat/completions
        MiniMaxAPI-->>OpenAIHelper: JSON response
        OpenAIHelper-->>MinimaxProvider: BifrostChatResponse
        MinimaxProvider-->>Bifrost: BifrostChatResponse
    else Streaming
        MinimaxProvider->>OpenAIHelper: HandleOpenAIChatCompletionStreaming(streamingClient, ...)
        OpenAIHelper->>MiniMaxAPI: POST /v1/chat/completions (SSE)
        MiniMaxAPI-->>OpenAIHelper: SSE chunks
        OpenAIHelper-->>MinimaxProvider: chan BifrostStreamChunk
        MinimaxProvider-->>Bifrost: chan BifrostStreamChunk
    end
    Bifrost-->>Caller: response / stream
Loading

Reviews (5): Last reviewed commit: "[fix]: preserve MiniMax multimodal conte..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@core/providers/minimax/minimax.go`:
- Around line 106-125: Streaming completion methods are ignoring the request
path from context because they hardcode the OpenAI endpoint. Update
MinimaxProvider.TextCompletionStream to use GetPathFromContext for the URL path
instead of a fixed "/v1/completions", and apply the same change to the analogous
streaming methods in the other providers named in the review. Keep the existing
openai.HandleOpenAITextCompletionStreaming/openai.HandleOpenAIChatCompletionStreaming
delegation, but pass the context-derived path so BifrostContextKeyURLPath is
honored consistently for streaming and non-streaming calls.
🪄 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: CHILL

Plan: Pro Plus

Run ID: 858b9431-87b9-4f1d-b403-c7fc82466d95

📥 Commits

Reviewing files that changed from the base of the PR and between f244691 and d36435e.

📒 Files selected for processing (9)
  • core/bifrost.go
  • core/internal/llmtests/account.go
  • core/internal/llmtests/validation_presets.go
  • core/providers/minimax/cachedcontents.go
  • core/providers/minimax/minimax.go
  • core/providers/minimax/minimax_test.go
  • core/schemas/bifrost.go
  • core/utils.go
  • scripts/bifrost-migration-cli/model.go

Comment thread core/providers/minimax/minimax.go Outdated
Comment on lines +106 to +125
func (provider *MinimaxProvider) TextCompletionStream(ctx *schemas.BifrostContext, postHookRunner schemas.PostHookRunner, postHookSpanFinalizer func(context.Context), key schemas.Key, request *schemas.BifrostTextCompletionRequest) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
return openai.HandleOpenAITextCompletionStreaming(
ctx,
provider.streamingClient,
provider.networkConfig.BaseURL+"/v1/completions",
request,
openai.BearerAuthHeader(key),
provider.networkConfig.ExtraHeaders,
provider.networkConfig.StreamIdleTimeoutInSeconds,
providerUtils.ShouldSendBackRawRequest(ctx, provider.sendBackRawRequest),
providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse),
provider.GetProviderKey(),
nil,
postHookRunner,
nil,
nil,
provider.logger,
postHookSpanFinalizer,
)
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether sibling OpenAI-compatible providers (e.g., cerebras) apply GetPathFromContext consistently between unary and streaming chat/text completion calls.
rg -n -B2 -A2 'BaseURL\s*\+\s*"?(providerUtils\.GetPathFromContext)?' core/providers/cerebras/cerebras.go core/providers/groq/groq.go core/providers/perplexity/perplexity.go 2>/dev/null
rg -n 'GetPathFromContext' core/providers/cerebras core/providers/groq core/providers/perplexity core/providers/minimax

Repository: maximhq/bifrost

Length of output: 7515


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find streaming completion calls in OpenAI-compatible providers and compare them
# with their unary counterparts for GetPathFromContext usage.
rg -n -C 2 'provider\.streamingClient|HandleOpenAI.*Streaming|GetPathFromContext' core/providers/{minimax,cerebras,groq,perplexity,openrouter,ollama,parasail,nebius,xai,sgl} 2>/dev/null

# Focused scan for hardcoded streaming URLs that skip GetPathFromContext.
rg -n 'streamingClient.*"\s*\+|BaseURL\s*\+\s*"/v1/|BaseURL\s*\+\s*"/chat/' core/providers/{minimax,cerebras,groq,perplexity,openrouter,ollama,parasail,nebius,xai,sgl} 2>/dev/null

Repository: maximhq/bifrost

Length of output: 29685


Streaming paths should honor GetPathFromContext
TextCompletionStream/ChatCompletionStream hardcode the OpenAI path, so BifrostContextKeyURLPath is ignored for streaming requests while still working for non-streaming calls. The same pattern also shows up in Cerebras, Groq, OpenRouter, Parasail, Nebius, Ollama, xAI, SGL, and Perplexity, so the fix should be applied wherever streaming delegates to the OpenAI helpers.

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

In `@core/providers/minimax/minimax.go` around lines 106 - 125, Streaming
completion methods are ignoring the request path from context because they
hardcode the OpenAI endpoint. Update MinimaxProvider.TextCompletionStream to use
GetPathFromContext for the URL path instead of a fixed "/v1/completions", and
apply the same change to the analogous streaming methods in the other providers
named in the review. Keep the existing
openai.HandleOpenAITextCompletionStreaming/openai.HandleOpenAIChatCompletionStreaming
delegation, but pass the context-derived path so BifrostContextKeyURLPath is
honored consistently for streaming and non-streaming calls.

akshaydeo and others added 10 commits July 6, 2026 15:44
## Summary

Bumps several indirect Go dependencies to their latest patch/minor versions across the `framework`, `tests/cmd/seed`, `tests/cmd/e2eseed`, and `tests/cmd/seedvks` modules.

## Changes

- `github.com/ClickHouse/ch-go` upgraded from `v0.61.5` → `v0.65.0`
- `github.com/hashicorp/go-version` upgraded from `v1.6.0` → `v1.7.0`
- `github.com/pierrec/lz4/v4` upgraded from `v4.1.21` → `v4.1.22`
- `github.com/maximhq/bifrost/core` upgraded from `v1.6.2` → `v1.6.3` (in `e2eseed`, `seed`, and `seedvks`)
- `go.sum` files updated with additional transitive dependency checksums introduced by the `ch-go` upgrade (e.g., `go-faster/city`, `go-faster/errors`, `segmentio/asm`, `shopspring/decimal`, `paulmach/orb`, and various `golang.org/x/*` historical entries)

## Type of change

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

## Affected areas

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

## How to test

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

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

No security implications. All changes are indirect dependency version bumps with no API surface changes.

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

Adds the `v2.0.0` branch as a trigger for the release pipeline so that releases can be cut directly from that branch in addition to `main`.

## Changes

- Added `v2.0.0` to the list of branches that trigger the release pipeline on push, enabling the pipeline to run when changes are pushed to the `v2.0.0` 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

Push a commit to the `v2.0.0` branch and verify the release pipeline is triggered automatically.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

No security implications. This only affects which branches trigger the CI release pipeline.

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

Bumps several indirect Go dependencies to their latest patch/minor versions across all modules in the repository.

## Changes

- `github.com/andybalholm/brotli`: `v1.2.1` → `v1.2.2`
- `github.com/ClickHouse/ch-go`: `v0.61.5` → `v0.65.0`
- `github.com/ClickHouse/clickhouse-go/v2`: `v2.30.0` → `v2.32.0`
- `github.com/hashicorp/go-version`: `v1.6.0` / `v1.7.0` → `v1.8.0`
- `github.com/pierrec/lz4/v4`: `v4.1.21` → `v4.1.22`
- `github.com/tidwall/pretty`: `v1.2.0` → `v1.2.1`
- `github.com/onsi/gomega` (transports only): `v1.35.1` → `v1.38.2`

## Type of change

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

## Affected areas

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

## How to test

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

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

No security implications. These are routine dependency version bumps with no API surface changes.

## 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
… guardrails (maximhq#4169)

## Summary

This PR introduces reversible redaction support across the Bifrost stack, allowing enterprise guardrails plugins to redact PII from log content while preserving an encrypted reversible mapping that authorized users can later reveal inline in the log detail view.

## Changes

- Added `RedactionPayload` schema type and associated context helpers (`RedactionPayloadFromContext`, `SetRedactionPayloadOnContext`, `ApplyLiteralReplacements`) to carry request-scoped redaction data from guardrails to log sinks
- Added `BifrostContextKeyRedactionData` context key for guardrails plugins to attach redaction payloads (marked DO NOT SET MANUALLY)
- Added `RedactionData` (transient), `RedactionMapping` (persisted), and `HasReversibleRedaction` (virtual) fields to the `Log` table struct
- Added `migrationAddRedactionMappingColumn` to persist the reversible mapping alongside the log row so it shares the row's lifecycle
- Updated `FindByID` to use `ScopedDB` so point lookups honor caller-supplied query scope (e.g. Enterprise DAC), preventing out-of-scope ID access
- Added `attachLogRedactionData` in the logging plugin to copy guardrail redaction payloads into log entries before async writes, gated on content logging being enabled
- Exposed `HasReversibleRedaction` on log detail and list endpoints so the UI knows when a reveal toggle is applicable
- Added a `Reveal` RBAC operation and `canReveal` prop threading through `LogDetailSheet` → `LogDetailView`
- Added a "Show original values" toggle in the log detail header that calls a new `POST /logs/:id/reveal` endpoint and applies the returned mapping inline to all message text, reasoning, and refusal fields without mutating stored data
- Added `useRevealLogRedactionMappingMutation` RTK Query mutation and `LogRedactionRevealResponse` type
- Literal replacement applies longest-match-first ordering to avoid partial substitution of overlapping tokens

## 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/logstore/... ./plugins/logging/...

# UI
cd ui
pnpm i
pnpm build
```

To validate end-to-end:
1. Configure an enterprise guardrails plugin that sets `BifrostContextKeyRedactionData` with a `RedactionPayload` containing `ReversibleMappings`
2. Send a request containing PII through Bifrost
3. Open the log detail view — the "Show original values" toggle should appear only for users with the `Reveal` RBAC permission on `Logs`
4. Toggle reveal — placeholders like `[EMAIL-1]` should be replaced inline with their original values
5. Navigate to a different log — the toggle resets and the mapping is cleared from state

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

- The `RedactionMapping` column stores the reversible mapping encrypted when an encryption key is configured; the mapping is deleted when the log row is deleted, preventing orphaned sensitive data
- The reveal endpoint is gated behind a new `Reveal` RBAC operation so only authorized users can recover original PII values
- `BifrostContextKeyRedactionData` is explicitly marked DO NOT SET MANUALLY to prevent plugins from injecting arbitrary mappings
- `attachLogRedactionData` is a no-op when content logging is disabled, preventing sensitive payloads from leaking through the async write path
- `FindByID` now enforces query scope, closing a gap where a scoped caller could retrieve out-of-scope log rows by ID

## Checklist

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

## Summary

Adds trace-level redaction of span content attributes before traces are exported to observability plugins. Connectors can register raw-to-placeholder replacement maps on a trace; when the trace completes, all content-bearing span attributes (messages, prompts, tool arguments, etc.) are rewritten in-place before any plugin receives the trace. The replacement map is stored in an unexported field so it is never serialized or leaked to connectors.

## Changes

- Added `IsContentAttribute(key string) bool` to classify which span attribute keys may carry user or model content (messages, prompts, embeddings, tool arguments, reasoning text, etc.).
- Added `RedactAttributeValue(value any, replacements map[string]string) any` to apply literal replacements across `string`, `[]string`, and `[]any` attribute shapes.
- Added `redactionReplacements` as an unexported field on `Trace` so the map is never JSON-serialized and cannot be observed by connectors.
- Added `Trace.SetRedactionReplacements` to store a defensive copy of the replacement map, stripping empty keys.
- Added `Trace.ApplyRedactionReplacements` to walk every span, redact content attributes, and clear the map atomically.
- Added `Trace.Reset` cleanup to ensure pooled traces cannot carry redaction data across requests.
- Added `redactSpanAttributes` as a package-private helper that locks a single span and rewrites its content attributes.
- Added `SetTraceRedactionReplacements` to the `Tracer` interface and its `NoOpTracer` implementation.
- Wired `ApplyRedactionReplacements` into `Tracer.CompleteAndFlushTrace` so redaction runs before any observability plugin `Inject` call.
- Added `Tracer.SetTraceRedactionReplacements` in the framework tracing layer to look up the live trace and delegate to `Trace.SetRedactionReplacements`.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./core/schemas/... ./framework/tracing/...
```

Key scenarios covered by new tests:

- `TestIsContentAttribute` — verifies the content attribute classifier includes message, prompt, embedding, and tool fields while excluding metadata fields like model name and session ID.
- `TestTraceApplyRedactionReplacementsRedactsContentAttributes` — verifies replacements are applied to all spans and that non-content attributes are left untouched.
- `TestTraceRedactionReplacementsDoNotSerialize` — verifies the replacement map never appears in JSON output.
- `TestTraceResetClearsRedactionReplacements` — verifies pooled traces cannot retain replacement data.
- `TestTracer_CompleteAndFlushTraceRedactsContentBeforeInject` — end-to-end: replacements set before span population are applied before the observability plugin receives the trace.
- `TestTracer_SetTraceRedactionReplacementsSurvivesLaterObservabilityPlugins` — replacements set before plugin registration still take effect at flush time.

## Breaking changes

- [x] Yes
- [ ] No

The `Tracer` interface gains a new method `SetTraceRedactionReplacements`. Any external implementation of `Tracer` must add this method. The `NoOpTracer` implementation is provided as a reference no-op.

## Security considerations

The replacement map is stored in an unexported struct field (`redactionReplacements`) and is explicitly cleared after `ApplyRedactionReplacements` runs and during `Reset`. This prevents PII or secret values used as redaction keys from being serialized into trace payloads, retained across pooled trace reuse, or observed by observability plugin authors inspecting the exported `Trace` struct.

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

Adds documentation for Bifrost-managed guardrail redaction, two new PII guardrail providers (Microsoft Presidio and Azure AI Language PII), and a `POST /api/logs/{id}/reveal` endpoint for revealing reversible redaction mappings from Bifrost logs.

## Changes

- Added a new `Guardrail Redaction` reference page (`enterprise/guardrails/redaction.mdx`) covering the three redaction modes (`runtime`, `logs_only`, `runtime_reversible`), redaction strategies (`replace`, `mask`, `hash`), the reveal permission model, and connector export behavior.
- Added integration pages for Microsoft Presidio (`integrations/guardrails/presidio.mdx`) and Azure AI Language PII (`integrations/guardrails/azure-language-pii.mdx`), including configuration fields, authentication modes, and all four config formats (Web UI, API, config.json, Helm).
- Extended the Regex and Secrets Detection provider docs and config examples to include per-pattern `action`, `redaction_strategy`, `redaction_mode`, and `entity_type` fields.
- Updated the guardrails overview to list Presidio and Azure AI Language PII in the provider capability matrix, added a warning against combining provider-managed transformation with Bifrost-managed redaction on the same phase, and added a Redaction section summarizing the three modes.
- Updated the nav (`docs.json`) to add a `Providers` sub-group under Guardrails and surface the new Redaction, Presidio, and Azure AI Language PII pages.
- Added `POST /api/logs/{id}/reveal` to the OpenAPI spec (YAML and compiled JSON), gated by `Logs:Reveal`, returning a `LogRevealResponse` with a placeholder-to-original-value mapping. Added `has_reversible_redaction` to `LogEntry`.
- Added `Logs:Reveal` and `MCPToolGroups`/`MCPLogs` to the RBAC resource table.
- Added guardrail redaction notes to the Datadog connector, OTel, default observability, and log-exports pages explaining that exported content receives redacted or placeholderized values and that reveal mappings are not forwarded to connectors.

## Type of change

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

## Affected areas

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

## How to test

Review the rendered docs to confirm:

- The Guardrail Redaction page renders the mode matrix table and the redaction mode selector screenshot correctly.
- The Presidio and Azure AI Language PII pages appear under the Guardrails > Providers nav group.
- The `POST /api/logs/{id}/reveal` endpoint appears in the API reference with correct request/response schemas and a `403` for missing `Logs:Reveal` permission.
- The Regex and Secrets Detection config examples include `action`, `redaction_strategy`, and `redaction_mode` fields.
- Cross-links between the redaction page and provider pages resolve without 404s.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

- The `POST /api/logs/{id}/reveal` endpoint returns original sensitive values and is gated by the `Logs:Reveal` RBAC permission. The response is marked `Cache-Control: no-store`.
- Reveal mappings are stored only in Bifrost logs and are never forwarded to trace-export connectors, object storage payloads, or external observability destinations.
- When an encryption key is configured, the reveal mapping is encrypted before storage.
- If `disable_content_logging` is enabled, no reveal data is persisted.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
…hen forwarded on a content chunk (maximhq#4964)

The streaming chat accumulator reads finish_reason only from the highest-index
chunk (getLastChatChunkLocked). For providers that send finish_reason on the
final content chunk, the OpenAI-compatible handler forwards it on that chunk and
appends a synthetic terminal chunk (index + 1) whose finish_reason is nil to
avoid a duplicate client emission (the forwardedTerminalFinishReason guard from
maximhq#1995). The highest-index chunk therefore has a nil finish_reason while the real
one sits one index lower, so the accumulated response records null. Unlike the
sibling TokenUsage, Cost and CacheDebug fields at the same site, finish_reason
was assigned without a nil check. The accumulated value feeds the logging plugin
(entry.StopReason) and Maxim, so streaming logs recorded an empty stop reason
for these providers; non-streaming is unaffected.

Fall back to the newest chunk that actually carries a finish_reason only when the
highest-index chunk has none. Regression tests cover the content-chunk case and
the standard terminal-chunk case.

closes maximhq#4963

Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
@R-droid101
R-droid101 self-requested a review July 7, 2026 14:05
TejasGhatte and others added 11 commits July 7, 2026 20:08
## Summary

Azure media endpoints (Speech, Transcription, ImageGeneration, ImageEdit, VideoGeneration) were hardcoding `Authorization: Bearer <key>` authentication, ignoring Azure-specific auth mechanisms such as service principal tokens or `api-key` headers. This PR propagates Azure auth headers through the shared OpenAI handler functions so that Azure's authentication flow is respected for all media request types.

## Changes

- Added an `authHeaders map[string]string` parameter to `HandleOpenAISpeechRequest`, `HandleOpenAITranscriptionRequest`, `HandleOpenAIImageGenerationRequest`, `HandleOpenAIImageEditRequest`, and `HandleOpenAIVideoGenerationRequest`.
- Each handler now prefers caller-supplied `authHeaders` over the default `Bearer` token fallback. If `authHeaders` is empty or nil, it falls back to `BearerAuthHeader(key)` as before.
- The Azure provider now calls `getAzureAuthHeaders` before invoking each of these handlers and passes the result through.
- Non-Azure providers (OpenAI, Groq, vLLM, xAI) pass `nil` for `authHeaders`, preserving existing behavior.

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

Validate by configuring an Azure provider with service principal credentials and invoking Speech, Transcription, ImageGeneration, ImageEdit, and VideoGeneration endpoints. Confirm that requests are authenticated using the Azure-specific headers rather than a `Bearer` token, and that non-Azure providers continue to authenticate with `Bearer` tokens as expected.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

Auth headers sourced from `getAzureAuthHeaders` may contain short-lived tokens or API keys. These are passed only in-memory to the HTTP request headers and are not logged or persisted. Existing secret handling guarantees apply.

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

Redaction replacements are now tracked separately for request-side (input) and response-side (output) content rather than in a single flat map. This prevents input-phase redaction tokens from being applied to output attributes and vice versa, ensuring each replacement set is scoped to the content it was derived from.

## Changes

- Introduced `RedactionPhase` (`input` / `output`) and `RedactionMapsByPhase` to replace the flat `map[string]string` used in `RedactionData` and `Trace.redactionReplacements`.
- `SetRedactionReplacements` and `SetTraceRedactionReplacements` now require a `RedactionPhase` argument so callers explicitly declare which lifecycle phase produced the replacements.
- Span attribute redaction (`redactSpanAttributes`) selects the correct replacement map per attribute using a new `traceContentAttributeScopeForKey` classifier:
    - Input-only attributes (e.g. `AttrInputMessages`, `AttrPrompt`) receive only input replacements.
    - Output-only attributes (e.g. `AttrOutputMessages`, `AttrRespReasoningText`) receive only output replacements.
    - Mixed attributes (e.g. `AttrToolCallArguments`, `AttrToolCallResult`) receive a merged map of both phases.
- `IsContentAttribute` is now derived from `traceContentAttributeScopeForKey` to keep the two in sync.
- `RevealRedactionMapping` on `logstore.Log` changed from `map[string]string` to `*schemas.RedactionMapsByPhase`, and `LogRedactionMappingResolver` returns the same type.
- The `redaction_mapping` field in the log API response and OpenAPI schema is now a `{ input, output }` object instead of a flat map.
- The UI `LogEntry` type reflects the new shape, and `logDetailView` applies input and output reveal mappings independently to the appropriate content sections (request body, input messages, response body, output messages, reasoning, refusals, Responses API items).

## Type of change

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

## Affected areas

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

## How to test

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

# UI
cd ui
pnpm i
pnpm build
```

Verify that:

- Input-phase redaction tokens (e.g. `[EMAIL-1]`) are applied only to input attributes and request bodies.
- Output-phase redaction tokens (e.g. `[EMAIL-2]`) are applied only to output attributes and response bodies.
- The log detail reveal toggle restores original values in the correct content sections.
- The `redaction_mapping` field in log detail API responses serializes as `{ "input": {...}, "output": {...} }`.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

`SetTraceRedactionReplacements` now requires a `RedactionPhase` argument. Any custom `Tracer` or `LogRedactionMappingResolver` implementations must be updated to match the new signatures. The `redaction_mapping` field in log detail API responses has changed shape from a flat object to a `{ input, output }` object; API consumers that read this field will need to handle the new structure.

## Related issues

N/A

## Security considerations

Scoping replacements by phase reduces the risk of a redaction token from one phase incorrectly masking or revealing content in another phase. The reversible mapping (used for the `Logs:Reveal` feature) is now also phase-scoped, so revealed values are only substituted back into the content section they originated from.

## Checklist

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

OpenAICompactionRequest had no MarshalJSON, so its value-typed
OpenAIResponsesRequestInput field — whose only marshaler is a pointer
receiver — was emitted by default struct encoding as a JSON object
({"OpenAIResponsesRequestInputArray":null,"OpenAIResponsesRequestInputStr":null}),
which /v1/responses/compact rejects with "Invalid type for 'input':
expected a string, but got an object instead." omitempty on the value
field also never omitted an empty input.

Add a MarshalJSON mirroring OpenAIResponsesRequest: route `input` through
the union's marshaler (string/array) and omit it when empty, since a
previous_response_id-only compaction is valid.
…mhq#4569)

Rebased onto core/v1.5.21 (includes EnvVar, AliasConfig, etc).

Adds ExtraContent json.RawMessage to ChatStreamResponseChoiceDelta so
Gemini extended thinking markers (google.thought, thought_signature)
survive streaming through any Bifrost-based gateway/proxy.

Also adds ExtraContent deep-copy in DeepCopyChatMessage for the
tool-call path to prevent shared backing-array mutations in concurrent
streaming pipelines.

Upstream PR: maximhq#4569

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary

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

## Changes

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

## Type of change

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

## Affected areas

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

## How to test

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

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

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

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

## Screenshots/Recordings

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

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

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

## Checklist

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

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

## Changes

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

## Type of change

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

## Affected areas

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

## How to test

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

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

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

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

## Screenshots/Recordings

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

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

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

## Checklist

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

* fix(transcription): support diarized_json segments, fix ElevenLabs speaker passthrough

OpenAI's response_format=diarized_json (gpt-4o-transcribe-diarize) returns
segments with a string id, plus speaker/type fields, which crashed
unmarshalling into TranscriptionSegment's int id (maximhq#5002). Adds a distinct
TranscriptionDiarizedSegment type and decodes diarized_json separately in
both the OpenAI provider's normal and large-payload-passthrough paths (Azure
inherits the fix via the shared handler).

Since Segments and DiarizedSegments serialize under the same "segments" key,
BifrostTranscriptionResponse gets a custom MarshalJSON/UnmarshalJSON pair so
the shape round-trips correctly both on the wire and through
framework/logstore's persist/reload cycle.

Also:
- ElevenLabs' per-word speaker_id was decoded but never propagated into the
  canonical TranscriptionWord; added a Speaker field and wired it through.
- Multipart transcription parsing only whitelisted OpenAI's own fields,
  silently dropping provider-specific extras like ElevenLabs' diarize; now
  passes through unrecognized fields via ExtraParams.
- TranscriptionUsage.Seconds was *int, but OpenAI's duration-usage variant is
  fractional (e.g. 521.5) and would fail to parse; widened to *float64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* refactor(transcription): dedupe diarized_json decode struct

The diarized_json response shape was duplicated as two anonymous structs
(normal path and large-payload-passthrough path); pulled into a single
named type instead.

* fix(transcription): address review findings on round-trip and multipart parsing

- Empty diarized segment arrays (e.g. silent audio) were indistinguishable
  from empty verbose segments on reload, since both unmarshal successfully
  from "[]" - a diarized response with zero segments would silently lose its
  identity and, on re-marshal, drop the "segments" key OpenAI's diarized_json
  contract requires. Adds an "is_diarized" marker written whenever
  DiarizedSegments is set, used as the authoritative signal when present;
  falls back to the existing shape-sniffing for data persisted before the
  marker existed.
- Custom Marshal/UnmarshalJSON now use encoding/json instead of sonic, per
  this repo's core/schemas convention.
- transcription multipart parsing didn't extract temperature or
  timestamp_granularities into their typed fields (verified via the
  openai-python SDK's actual multipart encoding: plain "temperature" field,
  repeated "timestamp_granularities[]"), so they'd leak into ExtraParams
  instead of reaching the outbound OpenAI request. Extracted properly and
  excluded from the generic passthrough.
- new(expr) instead of an intermediate variable for the two *int/*float64
  seconds conversions, matching this repo's existing Go 1.26 convention.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
## Summary

Adds support for Anthropic's `container_upload` content block type, which is used to stage files into the code-execution container. Previously, these blocks were silently dropped during conversion between Anthropic and Bifrost formats.

## Changes

- Added `ResponsesInputMessageContentBlockTypeContainerUpload` (`"container_upload"`) to the Bifrost responses schema constants.
- Added handling for `AnthropicContentBlockTypeContainerUpload` in both the standard and grouped Anthropic→Bifrost responses converters, preserving `file_id` and `cache_control`.
- Added `toBifrostResponsesContainerUploadBlock()` helper on `AnthropicContentBlock` to mirror the existing image/document block converters.
- Added the reverse conversion path in `convertContentBlockToAnthropic` so `container_upload` blocks round-trip correctly from Bifrost→Anthropic.
- Updated `isEffectivelyEmptyContent` in the cursor integration to treat a message containing only a `container_upload` block (with a non-nil `file_id`) as non-empty, preventing it from being replaced by the `"..."` placeholder.
- Added round-trip tests covering the standard converter, the grouped (Bedrock-routed) converter, and the full integration normalization pipeline.

## Type of change

- [ ] Bug fix
- [x] 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/anthropic/... -run TestRoundTrip_ContainerUpload
go test ./core/providers/anthropic/... -run TestRoundTrip_ContainerUpload_Grouped
go test ./transports/bifrost-http/integrations/... -run TestAnthropicContainerUploadSurvivesNormalization
go test ./...
```

The `container_upload` block should survive Anthropic→Bifrost→Anthropic conversion with its `file_id` and `cache_control` intact, and should not be replaced by the empty-content `"..."` placeholder during normalization.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None. `file_id` values are opaque references to files already staged in Anthropic's infrastructure; no new secrets or PII are introduced.

## 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
* fix: pass container block from anthropic api

* feat: force single region config in vertex key config

---------

Co-authored-by: tejas ghatte <tejas@tejass-MacBook-Pro.local>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
…mhq#5046)

RefreshLiveModelsForProvider, OnKeyAdded, and OnKeyUpdated read the raw
(unfiltered) key list and scheduled a list-models fetch for every key,
including disabled ones. Core already filters disabled keys out of
ListModels key resolution, so a fetch scoped to a disabled key's ID was
guaranteed to fail with "no key found with id...", wasting per-key
goroutines and logging misleading "falling back onto the static
datasheet" warnings for every disabled key on a provider.

Closes maximhq#5037

Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
)

## Summary

Fixes a fatal `concurrent map iteration and map write` panic in observability exporters (Datadog, OTEL, etc.) that cannot be caught by `recover()`. When `CompleteAndFlushTrace` hands a trace to exporters, late writers (streaming span finalization, redaction) may still be mutating span attribute maps under the span lock. Exporters iterating those live maps — directly or via marshaling — race those writes and crash the process.

## Changes

- Added `Trace.SnapshotForExport()` which produces a deep copy of a trace with all attribute maps (trace-level, span-level, and span event-level) cloned under their respective locks, giving exporters a safe, immutable view of the trace.
- Added `Span.snapshotForExport()` as the per-span equivalent, cloning `Attributes` and `Events` under the span lock.
- `CompleteAndFlushTrace` now takes a single snapshot after redaction and passes `exportTrace` to all observability plugin `Inject` calls instead of the live `completedTrace`.
- `Span.Reset()` now acquires `s.mu` before clearing fields, preventing a straggling writer from triggering a fatal concurrent map access on `s.Attributes` during pool release.
- Span pointer identity is preserved within the snapshot (`RootSpan` and `Spans` entries refer to the same copied `*Span` values), so pointer-equality checks within exporters continue to work.
- Updated the `ObservabilityPlugin.Inject` doc comment to remove the misleading reference to pool-reuse races, since the snapshot now insulates exporters from that concern.
- Added `trace_snapshot_test.go` with a race-detector test (`TestSnapshotForExport_ConcurrentWriter`) that reproduces the original crash, and an isolation test (`TestSnapshotForExport_IsolatedCopy`) verifying mutations to the original do not bleed into the snapshot.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test -race ./core/schemas/... ./framework/tracing/...
```

The `TestSnapshotForExport_ConcurrentWriter` test will fatal without the fix when run with `-race`. With the fix, all tests should pass cleanly under the race detector.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

Attribute maps containing PII or secrets are cloned by reference — values are not deep-copied. Redaction is applied before the snapshot is taken, so no new PII exposure is introduced.

## 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
fus3r and others added 22 commits July 12, 2026 19:13
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
…set (maximhq#5096)

* fix(anthropic): sanitize tool_use/tool_result ids to Anthropic's charset

Anthropic's Messages and Responses APIs require tool_use.id and
tool_result.tool_use_id to match ^[a-zA-Z0-9_-]+$. Bifrost forwarded
these ids straight through from whatever upstream provider produced
them, so replaying a conversation whose tool-call ids came from a
Kimi/Gemini-compatible backend (e.g. "functions.Bash:0") through
Bifrost to a real Claude model returned a 400 from Anthropic.

Add a deterministic, hash-based sanitizer (mirrors the existing
Bedrock tool-name aliasing pattern) and wire it into every site that
places a caller-supplied id into an outbound Anthropic tool_use,
tool_result, server_tool_use, or programmatic-tool-calling caller
reference, across both the Chat Completions and Responses API
surfaces, and both their streaming and non-streaming paths.

Verified end-to-end against the live OpenAI and Anthropic APIs: the
same non-conforming tool-call id that previously got a 400 from
Anthropic (streaming and non-streaming) now round-trips successfully.

* fix(anthropic): sanitize empty tool_use ids too

Anthropic's ^[a-zA-Z0-9_-]+$ pattern requires at least one character,
so an empty tool_use/tool_result id was being left unchanged by
SanitizeAnthropicToolUseID and would still fail validation. Route the
empty string through the same hash-based rewrite as non-conforming
ids instead of treating it as already-valid.

Found by Greptile's automated review on PR maximhq#5096.
…mhq#5102)

* [fix]: OpenAI provider - omit role from non-message Responses items

Affected packages:
- core/providers/openai/
- core/changelog.md

Closes maximhq#5101.

* Update core/providers/openai/responses.go

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Signed-off-by: nettee <nettee.liu@gmail.com>

---------

Signed-off-by: nettee <nettee.liu@gmail.com>
Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
…urface (maximhq#5094)

* fix: round-trip anthropic redacted_thinking blocks on the responses surface

* fix: skip data-less redacted_thinking blocks before reserving an output index

---------

Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
…holder (maximhq#5045)

Fixes maximhq#5027. The placeholder was hardcoded to 0 instead of reflecting
the fetched global default (30s), unlike the sibling Tool Sync Interval
field which already does this correctly.
* [fix]: zero pooled ChannelMessage references on release

releaseChannelMessage returned ChannelMessage objects to the pool with
the embedded BifrostRequest (pointing at the fully parsed request body)
and the Context (holding per-request user values) still set, and put the
response/error channels back while they could still hold an undelivered
*BifrostResponse / BifrostError. Idle pooled objects therefore pinned
request- and response-sized allocations until their next reuse — with
large request bodies and a high pool high-water mark this retains
significant heap indefinitely.

Zero the embedded request and context and drain both channels before
Put. Behavior on reuse is unchanged: getChannelMessage overwrites the
request, the caller sets Context immediately after acquire, and the
acquire-side channel drains remain as defense in depth. This also aligns
the function with the repo rule that every pooled object must have all
fields zeroed before pool.Put().

Affected packages/files:
- core/bifrost.go
- core/bifrost_test.go
- core/changelog.md

* [test]: cover streaming ResponseStream drain in release test

Adds a streaming variant of TestReleaseChannelMessage_ClearsPooledReferences
that pre-populates msg.ResponseStream and verifies it is drained and
cleared on release, per review feedback.

Affected packages/files:
- core/bifrost_test.go

---------

Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
…ence numbers (maximhq#4899)

* [fix]: framework/streaming - preserve terminal responses-stream event when providers reuse sequence numbers

The responses stream accumulator dedupes chunks by ChunkIndex and reads
usage from the highest index. Providers that omit or reuse
sequence_number deliver response.completed with an already-seen index,
so the terminal event carrying response.usage was dropped and LLM Logs
persisted 0/0 tokens. Terminal response events now get a reserved
trailing chunk index (same pattern as TerminalErrorChunkIndex),
idempotent across duplicate final processing.

Affected packages:
- framework/streaming/accumulator.go
- framework/streaming/types.go

Fixes maximhq#4846

* [fix]: framework/streaming - seed terminal chunk reservation on first delivery

A monotonic terminal chunk (unique highest index) skipped the
reservation branch, so a duplicate delivery of the same final chunk
(logging + maxim plugins both process it) minted a fresh index and
double-appended the terminal chunk into the persisted raw log. The
reservation is now seeded on the first terminal delivery, and the
duplicated reservation logic is extracted into a shared
reserveTerminalChunkIndex helper used by both the response and error
terminal paths.

---------

Co-authored-by: nnNyx <64274427+nnNyx@users.noreply.github.com>
* [fix]: OpenAI provider - enable rerank for custom OpenAI-compatible providers

Custom providers with base_provider_type openai are backed by
OpenAIProvider, whose Rerank stub unconditionally returned
unsupported_operation, so /v1/rerank never reached upstream even when
the upstream (omlx, llama.cpp, vLLM, ...) implements it. Rerank now
routes to the upstream /v1/rerank (honoring allowed_requests gating and
request path overrides) for custom providers; native OpenAI stays
unsupported.

Affected packages:
- core/providers/openai/openai.go
- core/providers/openai/rerank.go

Fixes maximhq#4834

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [fix]: OpenAI provider - parse rerank JSON in-process and preserve upstream documents

Addresses greptile review on PR maximhq#4897: skip PrepareResponseStreaming so
the large-response threshold cannot route structured rerank JSON onto
the stream-only path (which returned Results: nil), and only backfill
return_documents when the upstream did not already return a document.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [fix]: OpenAI provider - map rerank search_units billing and wire large-payload passthrough

Addresses CodeRabbit review on PR maximhq#4897: Cohere-shaped rerank upstreams
bill via meta.billed_units.search_units (token counts null), which was
dropped; and large-payload passthrough mode sent an empty upstream body
because SetBody(nil) skipped the staged stream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [refactor]: OpenAI provider - move rerank types to types.go and handler to openai.go per code conventions

Rerank wire types are now exported in types.go alongside sibling
operation types, converters stay in rerank.go (embedding.go/speech.go
pattern), and HandleOpenAIRerankRequest lives in openai.go next to the
Rerank method. Documents custom-provider rerank support in
docs/providers/custom-providers.mdx and
docs/quickstart/gateway/reranking.mdx.

---------

Co-authored-by: nnNyx <64274427+nnNyx@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… on the Responses path (maximhq#4786)

* fix domains

* docs update

* fix: use `parseAsSafeArrayOf` for logs page array query params to handle special characters in URLs (#4714)

## Summary

Array-type query parameters in the logs page (models, providers, etc.) were not using the safe URI-encoding parser, meaning values containing characters like `://` (e.g. model names such as `gpt://host/model`) could be misinterpreted as path or query delimiters by TanStack Router. This introduces `parseAsSafeArrayOf` and applies it consistently across all array filters.

## Changes

- Added `parseAsSafeArrayOf` to `queryParamsParser.ts` by composing `parseAsArrayOf` with the existing `parseAsSafeString` parser, ensuring full URI-encoding for comma-separated filter values.
- Replaced all usages of `parseAsArrayOf(parseAsString)` in the logs page with `parseAsSafeArrayOf` so that array filters (models, providers, aliases, status, etc.) benefit from the same encoding guarantees as string filters.
- Added unit tests for both `parseAsSafeString` and `parseAsSafeArrayOf` to verify round-trip correctness with model names containing `://`.

## Type of change

- [x] Bug fix

## Affected areas

- [x] UI (React)

## How to test

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

To manually verify, navigate to the logs page and apply a filter using a model name containing `://` (e.g. `gpt://host/model`). Confirm the URL encodes correctly and the filter persists on page reload without routing errors.

## Screenshots/Recordings

N/A

## Breaking changes

- [x] No

## Related issues

https://github.com/maximhq/bifrost/issues/4603

## Security considerations

No security implications. This change only affects URL query parameter encoding in the UI.

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

* feat: add TruncatedLabel component with tooltip on overflow (#4715)

## Summary

Adds a reusable `TruncatedLabel` component that displays truncated text with an automatic tooltip when the content overflows its container.

## Changes

- Introduces `TruncatedLabel`, a `<span>`-based component that detects when its text content is truncated via CSS overflow and conditionally renders a `Tooltip` to show the full content
- Truncation detection is performed by comparing `scrollWidth` to `clientWidth`, and re-evaluated on window resize or when `children` changes
- The tooltip content defaults to the `children` value if it is a string, but accepts an explicit `tooltip` prop for custom content
- The tooltip is only rendered when the text is actually truncated, avoiding unnecessary DOM overhead when content fits

## Type of change

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

## Affected areas

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

## How to test

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

Render a `TruncatedLabel` inside a constrained-width container with a long string. Verify that hovering over the truncated text shows a tooltip with the full content, and that no tooltip appears when the text is not truncated.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

No security implications. The component renders user-provided React nodes, which is consistent with existing UI patterns.

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

* refactor: extract `TruncatedLabel` component and replace inline truncation logic (#4716)

## Summary

Extracts the truncated label logic (truncate text with a tooltip on overflow) into a shared `TruncatedLabel` component and replaces all inline implementations with it.

## Changes

- Added a reusable `TruncatedLabel` component in `ui/components/ui/truncatedLabel` that handles text truncation and conditionally renders a tooltip when the content overflows
- Removed the local `TruncatedName` component from the providers page, which duplicated this logic using `useRef`, `useState`, and a resize event listener
- Replaced inline `<span className="truncate ...">` elements in the logs and MCP filter sidebars with `TruncatedLabel`

## Type of change

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

## Affected areas

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

## How to test

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

Verify that truncated labels in the providers list and filter sidebars still show a tooltip on hover when the text overflows, and no tooltip when it does not.

## Screenshots/Recordings

Verify the providers sidebar and log/MCP filter sidebars visually behave the same as before — truncated text shows a tooltip, non-truncated text does not.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

https://github.com/maximhq/bifrost/issues/4604

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

* fix: update audit logs page layout classes and add newline at EOF (#4719)

## Summary

Fixes the layout of the Audit Logs page to correctly fill the viewport and apply the appropriate background and border styles.

## Changes

- Replaced `h-[calc(100dvh-1rem)]` with `h-[calc(100vh-16px)]` for consistent viewport height calculation
- Swapped `mx-auto flex flex-col p-4` utility classes for `no-border-parent bg-background flex` to align with the layout conventions used elsewhere in the app
- Added missing newline at end of file

## Type of change

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

## Affected areas

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

## How to test

Navigate to the Audit Logs page and verify:
- The page fills the full viewport height without overflow or clipping
- The background color and border styling match the rest of the workspace layout

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

## Screenshots/Recordings

Add before/after screenshots showing the corrected Audit Logs page layout.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

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

* feat: add collapsible tag limit to TagInput (#4730)

## Summary

Adds collapsible tag support to the `TagInput` component and applies it to the keyword lists on the Complexity Router page. When a keyword list exceeds a configurable limit, tags beyond that limit are hidden behind a gradient overlay with a "Show more" toggle, keeping the UI compact while still allowing full access to all tags.

## Changes

- Added `collapsedTagLimit` and `expandButtonTestId` props to `TagInput`. When `collapsedTagLimit` is provided, the component renders in a collapsible layout: tags beyond the limit are hidden with a fade gradient, and "Show more" / "Show less" buttons toggle the expanded state. The collapsed state auto-resets when the tag count drops back to or below the limit.
- Set `KEYWORD_COLLAPSED_LIMIT = 8` on the Complexity Router page and passed it along with a `expandButtonTestId` to each keyword `TagInput`.
- Standardized border radius tokens from `rounded-lg`/`rounded-md`/`rounded-full` to `rounded-sm` across the Complexity Router page for visual consistency.
- Reformatted `index.html` inline shell skeleton from a single minified line to readable, indented HTML and CSS.

## Type of change

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

## Affected areas

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

## How to test

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

1. Navigate to the Complexity Router page.
2. Add more than 8 keywords to any keyword list.
3. Verify that tags beyond 8 are hidden with a gradient overlay and a "Show more" button appears.
4. Click "Show more" and confirm all tags are visible with a "Show less" button.
5. Click "Show less" and confirm the list collapses again.
6. Remove tags until 8 or fewer remain and confirm the list stays expanded without the toggle controls.

## Screenshots/Recordings

Before/after screenshots of the keyword lists with collapse behavior recommended.

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

* fix: rebuild token_usage from denormalized columns in hybrid log list (#4722)

* fix: rebuild token_usage from denormalized columns in hybrid log list

* refactor: inline hybrid token usage reconstruction

* fix: preserve malformed serialized token usage state

---------

Co-authored-by: gexiangdong <xiangdong.ge@pandasofcaribbean.com>

* refactor: centralize Anthropic request building into `BuildAnthropicChatRequestBody` and `AnthropicProviderRequestDefaultsMap` (#3309)

## Summary

This PR consolidates Anthropic-family request building across all providers (Anthropic native, Azure, Vertex, Bedrock) into two shared builder functions — `BuildAnthropicChatRequestBody` and `BuildAnthropicResponsesRequestBody` — eliminating duplicated inline logic and provider-specific wrapper helpers that previously scattered the same field-stripping, beta-header injection, and model-field manipulation across multiple files.

## Changes

- Introduced `AnthropicProviderRequestDefaults` and `AnthropicProviderRequestDefaultsMap` to encode static, per-provider request-shaping flags (e.g. `DeleteModelField`, `DeleteStreamField`, `AddAnthropicVersion`, `InjectBetaHeadersIntoBody`) in one place. Callers no longer pass these flags directly; the builder looks them up by `cfg.Provider`.
- Renamed `Deployment` to `Model` in `AnthropicRequestBuildConfig` for clarity, since all providers now use the same field for model/deployment overrides.
- Added `BuildAnthropicChatRequestBody` as the chat-completion analogue of `BuildAnthropicResponsesRequestBody`, covering both raw-body and typed paths, including field stripping, beta-header injection, streaming flag handling, and `fallbacks` deletion.
- Bedrock now routes Anthropic models through the Anthropic Messages API format (`invoke` / `invoke-with-response-stream` endpoints) for both chat and responses, rather than the Bedrock Converse API. This includes proper response parsing via `AcquireAnthropicMessageResponse` and streaming via `AnthropicStreamState` / `AnthropicResponsesStreamState`.
- Removed private wrapper functions `getRequestBodyForResponses` (Anthropic), `getRequestBodyForAnthropicResponses` (Azure, Vertex), and the inline `CheckContextAndGetRequestBody` closures for Anthropic models in Vertex and Azure, replacing all call sites with direct `BuildAnthropicChatRequestBody` / `BuildAnthropicResponsesRequestBody` calls.
- Exported `AcquireAnthropicResponsesStreamState`, `ReleaseAnthropicResponsesStreamState`, `AcquireAnthropicMessageResponse`, and `ReleaseAnthropicMessageResponse` so Bedrock can reuse the Anthropic stream state pool.
- Removed `DefaultVertexAnthropicVersion` constant from the Vertex package; the canonical version string now lives in `AnthropicProviderRequestDefaultsMap`.
- Bedrock's `releaseBedrockChatResponse` now zeroes the struct before returning it to the pool.
- `stripUnsupportedAnthropicFields` is now called inside `BuildAnthropicResponsesRequestBody` on the typed path, making field stripping symmetric across raw and typed paths and across both APIs.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [x] 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/...
go test ./core/providers/azure/...
go test ./core/providers/bedrock/...
go test ./core/providers/vertex/...
go test ./...
```

Run integration tests against Anthropic, Azure (Anthropic models), Vertex (Claude models), and Bedrock (Claude models) for chat completion, streaming, responses, responses streaming, and count-tokens endpoints. Verify that raw-body passthrough requests produce the same field stripping and beta-header injection as typed requests.

## Breaking changes

- [x] Yes
- [ ] No

`AnthropicRequestBuildConfig` has a breaking field rename: `Deployment` → `Model`. Any external code constructing this struct directly must update the field name. The static shaping flags (`DeleteModelField`, `DeleteRegionField`, `AddAnthropicVersion`, `AnthropicVersion`, `StripCacheControlScope`, `RemapToolVersions`, `InjectBetaHeadersIntoBody`) have been removed from `AnthropicRequestBuildConfig` and are now looked up internally via `AnthropicProviderRequestDefaultsMap`; callers that set these fields must remove them.

## Related issues

## Security considerations

No new auth flows, secrets handling, or PII exposure introduced. Field stripping ensures provider-unsupported fields are not forwarded to external APIs.

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

* refactor: extract `HandleAnthropicChatCompletionRequest` / `HandleAnthropicResponsesRequest` and make `completeRequest` a package-level func shared by Anthropic, Azure, and Bedrock providers (#4394)

## Summary

The Anthropic provider's unary request logic was duplicated across the Anthropic, Azure, Bedrock, and Vertex providers. This PR extracts the core non-streaming request execution into a package-level `completeRequest` function and introduces two exported handler functions — `HandleAnthropicChatCompletionRequest` and `HandleAnthropicResponsesRequest` — that encapsulate the full build → send → parse pipeline for chat completions and the Responses API respectively. Azure and Bedrock now delegate directly to these shared handlers for Anthropic-family models instead of reimplementing request dispatch, response parsing, and raw request/response handling inline.

A secondary bug fix is included: the large-response streaming client was being activated for count-tokens requests (which should always be buffered) and skipped for all other requests — the condition was inverted.

## Changes

- Extracted `completeRequest` as a package-level function accepting explicit `client`, `headers`, `extraHeaders`, `betaHeaderOverrides`, `providerName`, and `logger` arguments, removing the method receiver dependency so it can be called by other providers.
- Added `anthropicRequestHeaders` as a provider method to build the `x-api-key` / `anthropic-version` header map, shared across `TextCompletion`, `ChatCompletion`, `Responses`, and `CountTokens`.
- Introduced `HandleAnthropicChatCompletionRequest` and `HandleAnthropicResponsesRequest` as exported functions that perform the full unary request lifecycle (body build, HTTP send, large-response detection, response parse, raw request/response attachment). These are now called by the Anthropic, Azure, and Bedrock providers.
- Removed `completeMantleRequest` from Bedrock — its logic is now covered by `completeRequest` inside the shared handlers.
- Azure's `ChatCompletion` and `Responses` methods now branch early for Anthropic-family models, calling the shared handlers with Azure-specific auth headers, and fall through to the OpenAI-compatible path otherwise, eliminating the post-response model-family branch.
- Fixed the inverted condition in `completeRequest` that caused the large-response streaming client to be used for count-tokens requests instead of being skipped for them.
- `AnthropicRequestBuildConfig` now carries `BetaHeaderOverrides` so callers do not need to pass it separately.

## Type of change

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

## Affected areas

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

## How to test

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

Validate that chat completions and Responses API requests succeed for Anthropic-family models routed through the Azure and Bedrock providers, and that count-tokens requests return buffered responses without triggering large-response mode.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

Auth headers (`x-api-key`, Bearer tokens, SigV4-signed headers) are applied last in `completeRequest`, after network-config extra headers, ensuring they cannot be overridden by user-supplied configuration. No new secrets or PII handling paths are introduced.

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

* refactor: introduce `BearerAuthHeader` helper and migrate OpenAI-compatible providers from `schemas.Key` to `map[string]string` auth header param (#4425)

## Summary

This PR standardizes how Bearer token authentication headers are constructed across all OpenAI-compatible providers. Previously, each call site independently built the `Authorization: Bearer <token>` header map with duplicated inline logic. A new `BearerAuthHeader(key)` helper is introduced in the OpenAI package and used uniformly everywhere.

Additionally, the Azure provider's private `completeRequest` method is removed. Its non-Anthropic request paths (text completion, chat completion, responses, embedding, compaction) are now delegated directly to the shared `Handle*` functions in the OpenAI package, consistent with how other providers already work. The `Handle*` functions themselves are updated to accept a pre-built `authHeader map[string]string` instead of a raw `schemas.Key`, making them provider-agnostic and compatible with non-Bearer auth schemes (e.g., Azure API key headers, SigV4).

## Changes

- Added `BearerAuthHeader(key schemas.Key) map[string]string` to the OpenAI provider package, which returns an `Authorization: Bearer <token>` header map, or an empty map when the key carries no value.
- Updated all `Handle*Request` and `handleOpenAILargePayloadPassthrough` function signatures to accept `authHeader map[string]string` instead of `schemas.Key`, applying the map directly to request headers.
- Replaced all inline `var authHeader map[string]string` + conditional assignment blocks across Cerebras, Fireworks, Groq, HuggingFace, Mistral, Nebius, Ollama, Opencode, OpenRouter, Parasail, Perplexity, SGL, VLLM, xAI, and Bedrock with calls to `openai.BearerAuthHeader(key)`.
- Removed the Azure provider's `completeRequest` method and replaced its usage in `TextCompletion`, `ChatCompletion`, `Responses`, `Embedding`, and `Compaction` with direct calls to the corresponding shared OpenAI `Handle*` functions, passing Azure-specific auth headers and pre-resolved endpoint URLs.

## Type of change

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

## Affected areas

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

## How to test

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

Verify that all OpenAI-compatible providers (OpenAI, Azure, Cerebras, Fireworks, Groq, HuggingFace, Mistral, Nebius, Ollama, Opencode, OpenRouter, Parasail, Perplexity, SGL, VLLM, xAI, Bedrock Mantle) continue to authenticate correctly and that requests succeed for text completion, chat completion, responses, embeddings, and compaction endpoints.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

The `BearerAuthHeader` helper preserves the existing behavior of omitting the `Authorization` header when the key value is empty, which is intentional for providers that supply auth via other mechanisms (e.g., extra headers or SigV4 signing). No secrets are logged or exposed.

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

* refactor: replace pre-built SigV4 body signing with lazy `BodySigner` closure passed through request handlers (#4735)

## Summary

Replaces the pre-build-and-sign approach for Bedrock Mantle SigV4 authentication with a `BodySigner` callback that is invoked after the request handler has marshaled the body. This ensures the signature always covers the exact bytes sent on the wire, eliminating the previous double-marshal pattern where the body was built once for signing and again inside the handler.

## Changes

- Introduces a new `BodySigner` type (`func(jsonData []byte) (map[string]string, *schemas.BifrostError)`) in `core/providers/utils/bodysigner.go`. Handlers call it after building the request body and apply the returned headers to the outgoing request.
- Adds the `signer` parameter to `HandleOpenAIChatCompletionRequest`, `HandleOpenAIChatCompletionStreaming`, `HandleOpenAIResponsesRequest`, `HandleOpenAIResponsesStreaming`, `HandleAnthropicChatCompletionRequest`, `HandleAnthropicChatCompletionStreaming`, `HandleAnthropicResponsesRequest`, and `HandleAnthropicResponsesStream`. All existing callers pass `nil`.
- Rewrites Bedrock Mantle's SigV4 paths (`mantleChatCompletions`, `mantleChatCompletionsStream`, `mantleResponses`, `mantleResponsesStream`) to construct a `BodySigner` closure when no API key is present, instead of pre-building the body, signing it, and merging the signature headers into `extraHeaders`. The Bearer path no longer needs a separate early-return branch.
- Removes the now-unnecessary `maps` import and the intermediate `extraHeaders` map copies in the Mantle code paths.

## Type of change

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

## Affected areas

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

## How to test

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

For Bedrock Mantle with SigV4 (empty key value), verify that requests to chat completions, streaming chat completions, responses, and streaming responses are signed correctly and accepted by the Bedrock endpoint. For Bearer key paths, confirm that no signing is attempted and the `Authorization` header is set as expected.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

The `BodySigner` callback signs the exact serialized bytes that are placed on the wire. Previously, the body was serialized twice (once for signing, once inside the handler), which could in theory produce a signature mismatch if marshaling were non-deterministic. This change closes that gap by signing after the final body is set.

## Checklist

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

* feat: add `bedrock_mantle` as a first-class provider with native-Anthropic and OpenAI-compatible routing (#4736)

## Summary

Introduces `bedrock_mantle` as a first-class, standalone provider that owns the Bedrock Mantle surface (`bedrock-mantle.{region}.api.aws`). Previously, Mantle routing was handled as an internal routing decision inside the existing `bedrock` provider. The new provider gives operators a dedicated configuration surface for Claude (native Anthropic Messages API), OpenAI-compatible models (gpt-*), and Gemma models served through Mantle, without requiring a full Bedrock setup.

## Changes

- Added `schemas.BedrockMantle` (`"bedrock_mantle"`) as a new `ModelProvider` constant and registered it in `StandardProviders`, `dynamicallyConfigurableProviders`, `CanProviderKeyValueBeEmpty`, and `isKeySkippingAllowed`.
- Added `BedrockMantleKeyConfig` to the `Key` struct, carrying AWS credentials and region for SigV4 auth against the `bedrock-mantle` service. The existing `BedrockKeyConfig` is unchanged.
- Introduced the `core/providers/bedrockmantle` package implementing the full `Provider` interface. Chat, streaming chat, Responses, and streaming Responses dispatch by model family: Anthropic-family models use the native Anthropic Messages surface (`/anthropic/v1/messages`); all others use the OpenAI-compatible surface (`/v1` or `/openai/v1`). All other operations return unsupported-operation errors.
- Refactored `signAWSRequest` in the `bedrock` package to accept a `*BedrockKeyConfig` instead of individual credential fields, eliminating the now-redundant `signAWSRequestFromKey` wrapper. All call sites updated accordingly.
- Exported `SignMantleV4Headers` (previously `mantleSigV4Headers`, a method on `BedrockProvider`) so the new `bedrockmantle` package can sign requests without depending on the internal Bedrock provider struct. The function now supports both `BedrockKeyConfig` and `BedrockMantleKeyConfig` by mapping the latter into a synthetic `BedrockKeyConfig` for signing, and correctly handles GET requests (nil body) for the list-models path.
- Extended the Anthropic chat and Responses request builders to convert native structured outputs to tool calls for `BedrockMantle`, matching the existing `Vertex` workaround.
- Added `BedrockMantle` to the comprehensive LLM test harness (`ComprehensiveTestAccount`) with key config, provider config, and a full test file covering the supported scenarios (chat, streaming, tool calls, vision, structured outputs, prompt caching, reasoning, list models) and explicitly disabling unsupported ones.
- Marked `isMantleModel` in `bedrock/mantle.go` as deprecated in favour of the new provider.

## Type of change

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

## Affected areas

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

## How to test

Set AWS credentials and run the new provider test:

```sh
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...   # optional, for temporary credentials
export AWS_REGION=us-east-1

go test ./core/providers/bedrockmantle/... -v -run TestBedrockMantle
```

To run the full suite (skips Bedrock Mantle automatically when credentials are absent):

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

Configure a `bedrock_mantle` provider by supplying a `BedrockMantleKeyConfig` (or a Bearer API key in `Value`) with the desired region. The region can also be embedded as a prefix in the model ID (e.g. `us-west-2/anthropic.claude-haiku-4-5`) or set at the alias level via `AliasConfig.Region`.

## Breaking changes

- [ ] Yes
- [x] No

The `signAWSRequest` signature change is internal to the `bedrock` package and does not affect any public API. The `isMantleModel` function is deprecated but not removed.

## Security considerations

AWS credentials for `BedrockMantleKeyConfig` follow the same `SecretVar` resolution pattern used by `BedrockKeyConfig` (env-var references, never inlined literals). SigV4 signing is performed per-request on the exact body bytes that are sent, so the signature always covers what is transmitted. When a Bearer API key is present it takes precedence and no AWS credentials are required.

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

* feat: add `bedrock_mantle` provider with SigV4 key config, DB migration, and UI support (#4737)

## Summary

Adds `bedrock_mantle` as a first-class provider, enabling Bifrost to route requests to AWS Bedrock through a Mantle proxy endpoint. The provider supports the same SigV4 credential options as the existing Bedrock provider (inherited IAM role, explicit access/secret key, session token, AssumeRole) as well as a Bearer API key authentication mode.

## Changes

- Added `BedrockMantle` to the Anthropic passthrough allowlist in `clearAnthropicPassthroughForNonNativeProvider` so raw request bodies are preserved when routing through Bedrock Mantle.
- Added `BedrockMantleKeyConfig` redaction logic in `clientconfig.go`, mirroring the existing Bedrock redaction pattern.
- Added a new `migrationAddBedrockMantleKeyColumns` database migration that introduces seven `bedrock_mantle_*` SigV4 credential columns to the `config_keys` table.
- Extended `TableKey` with the seven Bedrock Mantle credential fields, along with `BeforeSave` serialization and `AfterFind` reconstruction hooks.
- Updated `mergeUpdatedKey` in the HTTP handler to correctly restore redacted Bedrock Mantle credential fields during key updates.
- Fixed `isClaudeModel` in the Anthropic integration to recognize `bedrock_mantle` (previously incorrectly matched `bedrock`) as a provider that can serve Claude models.
- Included `BedrockMantleKeyConfig` in the key hash inputs used by `mergeProviderKeys` and `reconcileProviderKeys` for config file/DB reconciliation.
- Added Bedrock Mantle credential redaction to `GetAllKeys`.
- Extended `config.schema.json` with `bedrock_mantle_key` and `provider_with_bedrock_mantle_config` definitions and registered `bedrock_mantle` as a valid provider name throughout the schema.
- Added UI support: provider icon (reusing the Bedrock SVG mark with a distinct gradient ID), model placeholder text, `isKeyRequiredByProvider` entry, label, form schema (`BedrockMantleKeyConfigSchema`), type definitions (`BedrockMantleKeyConfig`, `DefaultBedrockMantleKeyConfig`), and a full authentication method tab UI (IAM Role / Explicit Credentials / API Key) matching the Bedrock provider UX.
- Added `bedrock_mantle` to the Anthropic beta-headers provider family and the provider config sheet's Anthropic family list.
- Stripped the internal `_auth_type` field from `bedrock_mantle_key_config` before submitting the form payload.

## Type of change

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

## Affected areas

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

## How to test

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

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

Configure a `bedrock_mantle` provider in `config.json` or via the UI with one of the three auth methods:

- **IAM Role (Inherited):** set only `region`; leave access/secret key empty.
- **Explicit Credentials:** set `access_key`, `secret_key`, and `region`; optionally set `session_token`, `role_arn`, `external_id`, and `session_name`.
- **API Key:** set `region` and provide a Bearer token as the key `value`.

Send a request targeting a Claude model through the `bedrock_mantle` provider and verify the response is returned correctly and that credentials are redacted in the UI and API responses.

## Screenshots/Recordings

_Add before/after screenshots of the new Bedrock Mantle provider form and icon in the UI._

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

_Link related issues and discussions._

## Security considerations

- All seven Bedrock Mantle credential fields (`access_key`, `secret_key`, `session_token`, `region`, `role_arn`, `external_id`, `role_session_name`) are stored as `SecretVar` and are redacted in API responses and the UI, consistent with the existing Bedrock provider handling.
- The `_auth_type` discriminator field is stripped from the payload before it is persisted or transmitted.

## Checklist

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

* feat: add AWS Bedrock Mantle provider docs (#4738)

## Summary

Adds documentation for the AWS Bedrock Mantle provider, a distinct AWS endpoint (`bedrock-mantle.{region}.api.aws`) that exposes Claude models via the native Anthropic Messages API and OpenAI-family/Gemma models via an OpenAI-compatible API — all addressable through a single `bedrock_mantle/<model>` prefix in Bifrost.

## Changes

- Added a new `bedrock-mantle.mdx` provider page covering model ID formats, supported operations, all three authentication modes (SigV4 with explicit credentials, IAM role/inherited credentials, and Bearer API key), IAM role assumption via `role_arn`, and usage examples.
- Added the Bedrock Mantle configuration block to the `providers.mdx` config reference, with tabs for Static Credentials, IAM Role, and API Key (Bearer) auth modes.
- Added Bedrock Mantle to the provider capability matrix in `overview.mdx`.
- Registered `bedrock-mantle` in `docs.json` so it appears in the sidebar navigation.

## Type of change

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

## Affected areas

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

## How to test

Navigate to the Bedrock Mantle provider page and config reference in the rendered docs and verify:

- The sidebar entry for `bedrock-mantle` appears between `bedrock` and `cerebras`.
- All three auth tabs (Static Credentials, IAM Role, API Key) render correctly in both the provider page and the config reference.
- The capability matrix row for `bedrock_mantle/<model>` is present and accurate.
- Cross-links between the provider page and the config reference resolve correctly.

## Breaking changes

- [x] No

## Security considerations

Authentication credentials (`access_key`, `secret_key`, `session_token`, API keys) are documented using the `env.*` indirection pattern, consistent with how other providers handle secrets. No credentials are hardcoded in examples.

## Checklist

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

* tests: add `bedrock_mantle` provider capabilities and Postman environment config (#4739)

## Summary

Adds E2E test configuration and capability definitions for the `bedrock_mantle` provider, enabling it to be tested through the Bifrost V1 API test suite.

## Changes

- Added `bedrock_mantle` to `provider-capabilities.json` with `chat_completions`, `chat_completions_with_tools`, `responses`, `responses_with_tools`, and `list_models` enabled
- Added a new Postman environment file (`bifrost-v1-bedrock-mantle.postman_environment.json`) configured to use `anthropic.claude-opus-4-8` as the default model and `us-east-1` as the default region, with secret placeholders for API key, access key, secret key, and session token

## Type of change

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

## Affected areas

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

## How to test

Run the E2E test suite targeting the `bedrock_mantle` provider using the new Postman environment:

```sh
# Ensure the Bifrost server is running locally on port 8080
# Load the environment file and run the collection against bedrock_mantle

newman run tests/e2e/api/bifrost-v1.postman_collection.json \
  -e tests/e2e/api/provider_config/bifrost-v1-bedrock-mantle.postman_environment.json \
  --env-var "bedrock_mantle_api_key=<your_api_key>" \
  --env-var "bedrock_mantle_access_key=<your_access_key>" \
  --env-var "bedrock_mantle_secret_key=<your_secret_key>"
```

Expected outcome: chat completions, tool-use, responses, and model listing tests pass; all unsupported capability tests are skipped or return expected errors.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

The Postman environment file stores API key, access key, secret key, and session token as `secret` type fields with empty default values, ensuring credentials are not committed to the repository.

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

* chunking_strategy as extra params for openai models (#4741)

* fix: fix the provider governance form UI to only show calendar aligned toggle when budget is alignable (#4724)

## Summary

The calendar alignment toggle in the provider governance form was previously shown whenever any budget existed. This PR restricts its visibility and submission to only when at least one budget uses a calendar-alignable reset period (day, week, month, or year).

## Changes

- Introduced a `showCalendarAlignment` derived boolean that checks whether any configured budget has a reset duration supported by `supportsCalendarAlignment`.
- Replaced the previous condition (`watchedBudgets.length > 0`) with `showCalendarAlignment` to control rendering of the calendar alignment toggle.
- Updated the form submission payload so that `calendar_aligned` is only set to `true` when at least one budget actually supports calendar alignment — preventing the flag from being submitted for incompatible budget configurations.

## Type of change

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

## Affected areas

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

## How to test

1. Navigate to a provider's governance settings in the UI.
2. Add a budget with a reset duration that does **not** support calendar alignment (e.g., hourly). Verify the calendar alignment toggle does **not** appear.
3. Add or change a budget to use a calendar-alignable period (e.g., daily, weekly, monthly, yearly). Verify the toggle **does** appear.
4. Enable the toggle and save. Confirm `calendar_aligned: true` is included in the submitted payload.
5. Remove all calendar-alignable budgets and save. Confirm `calendar_aligned` is not set to `true` in the payload.

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

## Screenshots/Recordings

_Before:_ Calendar alignment toggle appears whenever any budget is present, regardless of reset period.

_After:_ Calendar alignment toggle only appears when at least one budget uses a day/week/month/year reset period.

## Breaking changes

- [x] No

## Related issues

## Security considerations

None.

## Checklist

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

* fix: custom providers with space in names could not set budget (#4725)

## Summary

Fixes a bug where updating or deleting provider-level governance for a custom provider whose name contains a space (e.g. `"OpenRouter Base"`) would return a 404. The UI percent-encodes the provider name in the URL path (`OpenRouter%20Base`), but the handler was comparing the raw encoded string directly against the stored provider name, causing the lookup to fail. Closes #4689

## Changes

- `updateProviderGovernance` and `deleteProviderGovernance` now call `url.PathUnescape` on the `provider_name` path parameter before using it, matching the decoded name against what is stored in the config store.
- Returns a `400` if the path parameter contains an invalid percent-encoding sequence.
- Added a regression test (`TestProviderGovernance_DecodesEncodedProviderName`) that seeds a provider with a space in its name, issues a PUT and DELETE using the percent-encoded path param, and asserts both succeed and persist correctly.
- Added a guard test (`TestProviderGovernance_UnknownProviderStill404`) to confirm that a genuinely unknown provider still returns 404 after the decode change.

## 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 ./transports/bifrost-http/handlers/... -run TestProviderGovernance
```

Expected output: all three `TestProviderGovernance_*` tests pass. Specifically:

- `TestProviderGovernance_DecodesEncodedProviderName` — PUT and DELETE with `OpenRouter%20Base` return `200`.
- `TestProviderGovernance_UnknownProviderStill404` — PUT with an unknown encoded name returns `404`.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

- Resolves #4689

## Security considerations

`url.PathUnescape` is used rather than `url.QueryUnescape` to correctly handle path-encoded characters. Invalid encoding sequences are rejected with a `400` rather than passed through, preventing malformed input from reaching the config store.

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

* fix: pass through gs:// image URLs on Vertex Gemini closes #4402 (#4568)

Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>

* Fix mcp reconnect failure on startup (#4316)

* Fix mcp reconnect failure on startup

* test: assert failed MCP client cleanup

---------

Co-authored-by: Gowtham <692171+HackToHell@users.noreply.github.com>

* fix(responses): preserve codex tool_search_call/tool_search_output input items (#4121)

* fix(responses): preserve codex tool_search_call/tool_search_output input items

Bifrost's Responses input deserializer rejected codex's tool-search follow-up
request with HTTP 400 "openai responses request input is neither a string nor an
array of responses messages", which hung/failed the agent turn. The fix teaches
ResponsesMessage about the two tool_search item types and round-trips them
verbatim. Background, since tool_search is non-obvious:

How codex's tool_search works (the path that hits this bug)
-----------------------------------------------------------
codex normally sends every MCP tool inline in the request `tools[]` as
`{type:"function", ...}`. But when a model's catalog has
`supports_search_tool: true` AND the tool count crosses
DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD (= 100) — e.g. an agent wired to several MCP
servers — codex stops sending them inline and "defers" them behind a discovery
tool:
  should_defer = supports_search_tool && (ToolSearchAlwaysDeferMcpTools || n >= 100)

The deferred flow is a two-request round-trip:

  1. Request 1: codex hides the deferred tools and instead declares one tool:
       {"type":"tool_search","execution":"client","description":"...",
        "parameters":{query, limit}}
     `execution:"client"` means the model does NOT run the search — codex does.

  2. The model emits a `tool_search_call` with `arguments` = {query, limit}.

  3. codex runs the search CLIENT-SIDE: a BM25 index over the deferred tool
     metadata (codex's ToolSearchHandler, core/src/tools/handlers/tool_search.rs,
     using the `bm25` crate). It picks the top-N matching tools.

  4. Request 2 (follow-up): codex appends two items to `input[]`:
       - {"type":"tool_search_call",   "call_id":..., "execution":"client",
          "arguments":{...}}
       - {"type":"tool_search_output", "call_id":..., "status":"completed",
          "execution":"client", "tools":[ {type:"function", ...the matches} ]}
     and also surfaces the discovered tools in `tools[]`. The model can now call
     them. This repeats as the model needs more tools.

Root cause
----------
ResponsesMessage (the element type of the Responses `input` array AND the
response `Output` array) doesn't model `tool_search_call` / `tool_search_output`:

  - The call's `arguments` is a JSON OBJECT, whereas function_call's `arguments`
    is a JSON STRING. So it cannot decode into ResponsesToolMessage.Arguments
    (*string) -> sonic.Unmarshal of the whole []ResponsesMessage errors ->
    OpenAIResponsesRequestInput.UnmarshalJSON falls through to the "neither a
    string nor an array" 400. The entire request dies before reaching OpenAI.
  - The output's `tools` array is also unmodeled (would be dropped/mangled,
    which OpenAI then rejects with "Missing input[N].tools[0].type").

OpenAI's Responses API supports both items natively (verified end-to-end against
the gateway: the tool_search tool spec is accepted and echoed; OpenAI validates
arguments-as-object and tools[].type). So this is purely a Bifrost modelling gap,
in the same family as the tool-type allowlist that already lists
ResponsesToolTypeToolSearch / ResponsesToolTypeNamespace — just a different code
path (input-item deserialization vs the request tools[] allowlist).

Fix
---
Add ResponsesMessageTypeToolSearchCall / ResponsesMessageTypeToolSearchOutput and
give ResponsesMessage custom (Un)MarshalJSON that preserves these two item types
verbatim (original bytes in, original bytes out), so the object `arguments` and
the `tools` array survive intact. Every other item type defers to the default
struct (de)coding, unchanged. One change covers both directions because request
input and response Output are both []ResponsesMessage.

Impact: unblocks codex tool-search deferral (multi-MCP-server / >=100-tool agents)
through Bifrost. Verified with a round-trip test reproducing the exact follow-up
payload, plus the existing providers/openai and schemas suites (no regressions).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(responses): reset ResponsesMessage receiver in UnmarshalJSON

Clear the receiver at the top of ResponsesMessage.UnmarshalJSON so a reused
instance never retains a stale rawToolSearch (or other field) from a prior
decode. Without this, unmarshalling a tool_search item and then a normal
message into the same value would leave the preserved bytes in place, and
MarshalJSON would re-emit them. Not reachable via the array-decode path (each
element starts zero), but a cheap, defensive correctness fix.

Addresses CodeRabbit review on PR #4121.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(openapi): correct prompt_cache_retention enum to in_memory

The chat schema declared the enum as [in-memory, 24h], but OpenAI's
actual accepted values are in_memory (underscore) and 24h. The hyphenated
form was a typo from when the enum was first added and never matched
OpenAI, so spec-generated clients produced Literal['in-memory', '24h']
and rejected the valid value with a pydantic literal_error.

The Go runtime treats prompt_cache_retention as a pass-through *string,
so no behavior changes — only the spec enum, the regenerated openapi.json,
and the doc comment are corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Suresh Kumar Ponnusamy <suresh@atomicwork.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>

* fix: build fix in vertex (#4765)

## Summary

Fixes a bug in the Vertex provider where the code path for handling Gemini/Gemma model families was duplicated, with the non-streaming branch incorrectly using `ToGeminiChatCompletionRequest` (without image URL scheme support) while the streaming branch used `ToGeminiChatCompletionRequestWithImageURLSchemes`. This consolidates the logic so both paths use the image URL scheme-aware converter.

## Changes

- Replaced `ToGeminiChatCompletionRequest` with `ToGeminiChatCompletionRequestWithImageURLSchemes` in the non-streaming Gemini/Gemma branch, making it consistent with the streaming branch
- Removed the duplicate non-streaming Gemini/Gemma and OpenAI handler blocks that had been incorrectly separated from the streaming path, consolidating them into a single unified code path

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

Send a chat completion request to the Vertex provider using a Gemini or Gemma model with image URL content. Verify that image URLs are correctly processed in both streaming and non-streaming modes.

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

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

* fix: responses.go build fix (#4766)

## Summary

Consolidates the two separate `UnmarshalJSON` implementations on `ResponsesMessage` into a single method that handles both the verbatim `tool_search` preservation and the `arguments` normalization logic. Previously, the file contained a duplicate `UnmarshalJSON` definition — the first handled `tool_search` items and fell back to a plain `sonic.Unmarshal`, while the second (the correct one) handled argument normalization. The duplicate caused the normalization path to be unreachable for non-`tool_search` items, meaning `tool_search_call` items with object-typed `arguments` would silently fail mid-stream and hang streaming clients.

## Changes

- Removed the redundant first `UnmarshalJSON` that short-circuited to `sonic.Unmarshal` without normalizing `arguments`, leaving only the correct implementation that handles both the `rawToolSearch` early-return and the `arguments` object-to-string normalization.
- Relocated `MarshalJSON` to follow `UnmarshalJSON` for logical grouping.
- The fix ensures `tool_search_call` items whose `arguments` field is a JSON object (e.g. `{}` while in-progress, `{"query":"...","limit":10}` when completed) are correctly stringified into the `*string` field expected by `ResponsesToolMessage`, preventing decode failures that previously dropped items silently.

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

Validate by sending a request that triggers `tool_search_call` streaming events and confirming that items with both `{}` (in-progress) and `{"query":"...","limit":10}` (completed) `arguments` values are decoded without error and do not hang the streaming client.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

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

* feat: add server/client_id filter to MCP clients list (#4767)

## Summary

Adds a `server` (client ID) filter to the MCP clients list endpoint and UI, allowing users to filter the MCP clients table to a specific server. The filter state is persisted in the URL via query parameters, enabling shareable and bookmarkable filtered views.

## Changes

- Added `ClientID` field to `MCPClientsQueryParams` and applied it as a `WHERE client_id = ?` clause in `GetMCPClientsPaginated`
- Exposed the filter via a new `server` query parameter on `GET /api/mcp/clients`
- Migrated the MCP registry page from local `useState` to `nuqs` `useQueryStates`, storing `search`, `server`, and `offset` in the URL
- Added `server` prop and `onServerFilterClear` callback to `MCPClientsTable`, rendering a dismissible "Server filter" badge/button when the filter is active
- Extended `GetMCPClientsParams` type and the RTK Query API call to pass the `server` parameter through to the backend
- `hasActiveFilters` now accounts for both `debouncedSearch` and `server`, preventing the empty state from showing while a server filter is active

## Type of change

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

## Affected areas

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

## How to test

1. Navigate to the MCP Registry page.
2. Confirm that `search`, `server`, and `offset` appear in the URL and survive a page refresh.
3. Set a `server` query param (e.g. `?server=<client_id>`) directly in the URL and verify the table filters to only that client.
4. Click the "Server filter" dismiss button and confirm the filter clears and the URL updates.
5. Verify that the empty state is not shown when a server filter is active but returns no results.

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

# UI
cd ui
pnpm i
pnpm build
```

## Screenshots/Recordings

_Add before/after screenshots showing the server filter badge and URL state._

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

_Link related issues here._

## Security considerations

The `client_id` filter is applied as a parameterised query (`WHERE client_id = ?`), so there is no SQL injection risk. No secrets or PII are exposed through the new filter parameter.

## Checklist

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

* fix: redact decoder details from invalid request payload errors (#4770)

## Summary

Error messages returned on JSON decode failures were leaking internal decoder details (e.g., field names, Go type information, and `cannot unmarshal` messages) back to API callers. This replaces all such messages with a single, generic `"Invalid request payload"` string to avoid exposing implementation internals.

## Changes

- Replaced all `fmt.Sprintf("invalid/Invalid request format: %v", err)` and similar patterns across handlers (`config`, `featureflags`, `governance`, `inference`, `mcp`, `mcp_per_user_headers`, `mcpinference`, `provider_keys`, `providers`, `session`) with the static string `"Invalid request payload"`.
- Removed now-unused `fmt` import from `mcpinference.go`.
- Added `requestpayload_test.go` with two tests that assert the generic message is returned and that decoder internals (`cannot unmarshal`, field names, Go struct details) are not present in the response body or error string.
- Updated the existing `governance_test.go` assertion for the unknown-field case to expect `"Invalid request payload"` instead of `"unknown field"`.

## Type of change

- [ ] 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 ./transports/bifrost-http/handlers/...
```

Confirm that:
- `TestSessionLoginInvalidPayloadDoesNotExposeDecoderDetails` passes and the response body contains `"Invalid request payload"` with no decoder internals.
- `TestPrepareRequestInvalidPayloadDoesNotExposeDecoderDetails` passes and the returned error is exactly `"invalid request payload"`.
- `TestComplexityAnalyzerConfigPutRejectsInvalidPayloads` passes with the updated `"Invalid request payload"` expectation for the unknown-field case.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

Decoder error messages from Go's `encoding/json` and `sonic` can expose internal struct field names, type information, and value details. Returning these verbatim in HTTP responses constitutes an information disclosure risk. This change ensures all parse-failure responses return a fixed, opaque message regardless of the underlying decode error.

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

* feat(anthropic): forward server-side tool_search streaming results

Anthropic's server-side tool_search emits server_tool_use(tool_search) ->
tool_search_tool_result(tool_references) -> tool_use(discovered tool) in one
streamed response. The responses stream converter had no handler for these
blocks, so it dropped the tool_references and emitted orphan function_call
argument deltas (args with no parent item) that desync the client parser.

Mirror the existing web_search_tool_result path: recognize the tool_search
server_tool_use + tool_search_tool_result blocks, emit a tool_search_call item
(added on the query block, completed on the result block carrying the discovered
tool references), suppress the query arg deltas, and skip the spurious generic
done. The follow-up tool_use is forwarded unchanged by the generic path.

Adds ResponsesMessageTypeToolSearchCall + ResponsesToolSearchCall to the schema.
Only touches tool_search branches; web_search/web_fetch/advisor logic unchanged.

Refs maximhq/bifrost#4780

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Charlie Chen <charliec@zetachain.com>

* test(anthropic): cover tool_search bm25 + completed response; set Name on done item

Address review feedback on the tool_search streaming patch:
- carry the tool name on the tool_search_call output_item.done (parity with the
  added item and the advisor_call done path) via a new ToolSearchToolName state field
- table-drive the tool_references test over both regex and bm25 variants
- assert the terminal response.completed Output includes the tool_search_call
  with its tool_references (covers the OutputItems persistence path)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Charlie Chen <charliec@zetachain.com>

* feat(anthropic): rebuild tool_search_call on the Bifrost to Anthropic path

The forward streaming path now emits a tool_search_call carrying the discovered
tool_references, but the request builder (ConvertBifrostMessagesToAnthropicMessages)
had no case for it, so on a follow-up turn the item hit default: continue and was
dropped — losing the server-side tool_search context (unlike web_search/advisor,
which round-trip).

Add convertBifrostToolSearchCallToAnthropicBlocks + a ResponsesMessageTypeToolSearchCall
case mirroring the web_search/advisor pattern: rebuild server_tool_use(tool_search_*)
followed by its tool_search_tool_result carrying the tool_references, so the pair stays
in the assistant message on subsequent turns. Adds a pure-function reverse-path test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Charlie Chen <charliec@zetachain.com>

* fix: update audit logs page layout classes and add newline at EOF (#4719)

## Summary

Fixes the layout of the Audit Logs page to correctly fill the viewport and apply the appropriate background and border styles.

## Changes

- Replaced `h-[calc(100dvh-1rem)]` with `h-[calc(100vh-16px)]` for consistent viewport height calculation
- Swapped `mx-auto flex flex-col p-4` utility classes for `no-border-parent bg-background flex` to align with the layout conventions used elsewhere in the app
- Added missing newline at end of file

## Type of change

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

## Affected areas

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

## How to test

Navigate to the Audit Logs page and verify:
- The page fills the full viewport height without overflow or clipping
- The background color and border styling match the rest of the workspace layout

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

## Screenshots/Recordings

Add before/after screenshots showing the corrected Audit Logs page layout.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

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

* feat: add collapsible tag limit to TagInput (#4730)

## Summary

Adds collapsible tag support to the `TagInput` component and applies it to the keyword lists on the Complexity Router page. When a keyword list exceeds a configurable limit, tags beyond that limit are hidden behind a gradient overlay with a "Show more" toggle, keeping the UI compact while still allowing full access to all tags.

## Changes

- Added `collapsedTagLimit` and `expandButtonTestId` props to `TagInput`. When `collapsedTagLimit` is provided, the component renders in a collapsible layout: tags beyond the limit are hidden with a fade gradient, and "Show more" / "Show less" buttons toggle the expanded state. The collapsed state auto-resets when the tag count drops back to or below the limit.
- Set `KEYWORD_COLLAPSED_LIMIT = 8` on the Complexity Router page and passed it along with a `expandButtonTestId` to each keyword `TagInput`.
- Standardized border radius tokens from `rounded-lg`/`rounded-md`/`rounded-full` to `rounded-sm` across the Complexity Router page for visual consistency.
- Reformatted `index.html` inline shell skeleton from a single minified line to readable, indented HTML and CSS.

## Type of change

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

## Affected areas

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

## How to test

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

1. Navigate to the Complexity Router page.
2. Add more than 8 keywords to any keyword list.
3. Verify that tags beyond 8 are hidden with a gradient overlay and a "Show more" button appears.
4. Click "Show more" and confirm all tags are visible with a "Show less" button.
5. Click "Show less" and confirm the list collapses again.
6. Remove tags until 8 or fewer remain and confirm the list stays expanded without the toggle controls.

## Screenshots/Recordings

Before/after screenshots of the keyword lists with collapse behavior recommended.

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

* fix: rebuild token_usage from denormalized columns in hybrid log list (#4722)

* fix: rebuild token_usage from denormalized columns in hybrid log list

* refactor: inline hybrid token usage reconstruction

* fix: preserve malformed serialized token usage state

---------

Co-authored-by: gexiangdong <xiangdong.ge@pandasofcaribbean.com>

* refactor: centralize Anthropic request building into `BuildAnthropicChatRequestBody` and `AnthropicProviderRequestDefaultsMap` (#3309)

## Summary

This PR consolidates Anthropic-family request building across all providers (Anthropic native, Azure, Vertex, Bedrock) into two…
)

Route ElevenLabs sound-generation models (e.g. eleven_text_to_sound_v2)
to the upstream /v1/sound-generation API by reusing the speech request
type. The provider dispatches internally based on the model id, so
existing text-to-speech flows and virtual-key governance (provider/model
allowlists, budgets, rate limits) are unchanged.

- Add ElevenlabsProvider.soundGeneration mapping SFX params
  (duration_seconds, loop, prompt_influence) with range clamping
- Add dedicated POST /v1/audio/sound-effects alias (also works via
  /v1/audio/speech); relax the voice requirement for sound models only
- Add SpeechUsage.AudioSeconds (additive) for future per-second billing
- Unit tests for model detection, request mapping, and clamping
- Document the feature, request params, and billing caveats
…maximhq#4901)

* [fix]: HTTP transport - attribute passthrough virtual keys sent via Azure api-key header

ConvertToBifrostContext recognized virtual keys from x-bf-vk,
Authorization, x-api-key, and x-goog-api-key, but not from api-key --
Azure OpenAI's native auth header used by Azure SDKs on passthrough.
The VK context key was never set, so governance/logging attributed the
call to the underlying base key. Recognition is gated on the sk-bf-
prefix; real Azure keys are untouched and the header is still dropped
before forwarding upstream.

Affected packages:
- transports/bifrost-http/lib/ctx.go

Fixes maximhq#4477

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: recognize azure api-key virtual keys

* [docs]: governance - document api-key header as a virtual key carrier

* [test]: governance - cover legacy unprefixed x-bf-vk acceptance

Addresses greptile review on PR maximhq#4901: the x-bf-vk prefix-guard removal
aligns the plugin parser with the HTTP transport extractor and the
documented behavior (legacy VKs without the sk-bf- prefix are only
supported via x-bf-vk); now covered by a test.

* [fix]: governance - restore sk-bf- prefix guard on x-bf-vk

Old virtual key formats are not supported; only the api-key header
recognition from this PR stays.

---------

Co-authored-by: nnNyx <64274427+nnNyx@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
… no runtime chown (maximhq#4902)

* [fix]: container image - support OpenShift arbitrary UIDs without CAP_CHOWN

The image assumed it runs as UID 1000 owning /app/data; the entrypoint
chown failed under OpenShift restricted-v2 (arbitrary UID, group 0, no
CAP_CHOWN) and the server died writing config.db. Writable dirs are now
group-0-owned and group-writable at build time, the runtime chown is
best-effort root-only, and the Helm chart no longer pins
runAsUser/fsGroup 1000 by default.

Affected packages:
- transports/Dockerfile, Dockerfile.local, Dockerfile.redhat
- transports/docker-entrypoint.sh
- helm-charts/bifrost/values.yaml
- .github/workflows/scripts/validate-helm-templates.sh

Fixes maximhq#4367

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [fix]: container image - keep fsGroup default for vanilla k8s, fail fast on unwritable data dir

Addresses review on PR maximhq#4902: restores podSecurityContext fsGroup 1000 +
runAsNonRoot (runAsUser stays removed; OpenShift users set
podSecurityContext: {}), fixes the unreachable root repair branch
(test -w always passes for UID 0), and exits 1 with actionable guidance
when a non-root process cannot write the data dir.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: fail fast when app dir is unwritable

* [fix]: helm chart - correct OpenShift fsGroup override guidance

Addresses CodeRabbit review on PR maximhq#4902: podSecurityContext: {} merges
with chart defaults and keeps fsGroup 1000, so the OpenShift note now
points at podSecurityContext.fsGroup: null (verified the rendered
manifest drops the field); the entrypoint hint now references
podSecurityContext.fsGroup, matching how the chart maps values.

* [fix]: container image - numeric USER, OpenShift-correct fsGroup guidance, write-check escape hatch

Addresses akshaydeo's review on PR maximhq#4902:
- USER 1000:0 in Dockerfile/Dockerfile.local so kubelet can verify
  runAsNonRoot without a pinned runAsUser (finding 1)
- chart README documents the restricted-v2 admission rejection and the
  podSecurityContext.fsGroup: null override (finding 2), incl. the
  root-only repair behavior change (finding 5)
- entrypoint advice split per platform; no more fsGroup 0 (finding 3)
- BIFROST_SKIP_WRITE_CHECK=1 downgrades the fail-fast exit for
  read-only/external-store deployments (finding 4)
- CI check greps bare runAsUser: and repeats on a postgres-mode render
  (finding 6)
- security.mdx runtime-stage excerpt gets group-0 ownership + numeric
  USER (finding 7)
- g=rwX guarded against APP_DIR=/app; DATA_UID/GID stat moved into the
  branches that use it (finding 8)

* [docs]: align security.mdx runtime excerpt with the shipped Dockerfile hardening

Addresses CodeRabbit follow-up on PR maximhq#4902: the excerpt now keeps /app
owned by appuser:appuser, chowns only APP_DIR to group 0, and guards
the g=rwX like the real Dockerfiles; adds a language tag to the README
admission-error fence (MD040).

---------

Co-authored-by: nnNyx <64274427+nnNyx@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…q#4322)

Both providers bill rerank per query, but neither API returns a usage
payload, so Usage stayed nil (Vertex) or carried only header-derived
input tokens (Bedrock, maximhq#3917) and rerank cost always computed to 0 —
NumSearchQueries was never set and datasheet rerank entries have zero
per-token rates.

- synthesize the billable query count at conversion time (one call is
  one query) in both rerank converters, mirroring the Cohere fix for
  maximhq#4239, using the Go 1.26 new(1) builtin per review
- merge Bedrock's header-derived input tokens into the synthesized
  Usage, still only filling token fields that are missing
- isolation tests confirming the converter sets only the query count
  (nothing to clobber) and records one query even for empty result sets

Fixes maximhq#4321
…eak admin PUTs (maximhq#5107)

Legacy v1.5.10 rows persisted allowed_models as the bare string * in a
serializer:json column; loading them aborts with invalid character '*'
and poisons every subsequent PUT /api/providers for that provider. Adds
a data-repair migration rewriting bare * to canonical ["*"] for both
allowed_models and blacklisted_models (current write path already
round-trips correctly; only legacy rows are affected).

Affected packages:
- framework/configstore/migrations.go

Fixes maximhq#4318
…errors (maximhq#4896)

* [fix]: core/schemas - make trace/span helpers nil-safe to prevent streaming error panic

A Bedrock streaming error finalizer could hit a nil span entry (or nil
receiver) in the tracing path (completeDeferredSpan -> Trace.GetSpan),
panicking the process instead of forwarding the stream error.

Affected packages:
- core/schemas/trace.go

Fixes maximhq#3455

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [test]: core/schemas - cover empty span ID guard in GetSpan

Addresses greptile review on PR maximhq#4896.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: nnNyx <64274427+nnNyx@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Added Trendshift badge to README for repository tracking.

Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Hey @octo-patch this can be used as a custom provider right? any specific reason to add it as a first party provider?

Merge the latest dev branch and preserve MiniMax image and video content blocks through the OpenAI-compatible request path.

Affected packages:
- core/schemas/
- core/providers/minimax/
- docs/openapi/
@octo-patch

Copy link
Copy Markdown
Author

Yes, its OpenAI-compatible surface can be configured as a custom provider. First-party support removes per-deployment custom configuration and provides a stable built-in identity and default endpoint, schema/UI/migration registration, documented regional setup, model listing, provider-specific parameter passthrough, and dedicated regression coverage.

@octo-patch

Copy link
Copy Markdown
Author

This PR has been replaced by #5184. The replacement is a single clean commit based on the latest dev branch and carries the provider implementation, tests, configuration, UI, and documentation changes.

@octo-patch octo-patch closed this Jul 14, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Aug 1, 2026
11 tasks
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.