[fix]: Anthropic - container param no longer dropped on /v1/messages, container reuse works - #5829
[fix]: Anthropic - container param no longer dropped on /v1/messages, container reuse works#5829AdityaPainuli wants to merge 139 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe Anthropic request converters now preserve ChangesAnthropic container conversion
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This preserves Anthropic container IDs and skills across request conversion so reused code-execution containers continue to work; no actionable merge-blocking risk remains beyond normal checks and review. Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
core/providers/anthropic/responses_test.go (1)
36-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest raw container values directly.
Both cases store
*AnthropicContainerthrough the forward converter. They do not test the new rawstringand object parsing path inToAnthropicResponsesRequest.Add direct egress cases with
ExtraParams["container"]set to a string ID and an object map. Verify the promoted typed field and removal fromExtraParams.🤖 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/anthropic/responses_test.go` around lines 36 - 93, Extend TestAnthropicContainerRoundTrip with direct ToAnthropicResponsesRequest cases that initialize ExtraParams["container"] as a raw string ID and as an object map, rather than using AnthropicMessageRequest.Container. Verify each conversion promotes the value into the corresponding AnthropicContainer typed field and removes "container" from ExtraParams.
🤖 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.
Nitpick comments:
In `@core/providers/anthropic/responses_test.go`:
- Around line 36-93: Extend TestAnthropicContainerRoundTrip with direct
ToAnthropicResponsesRequest cases that initialize ExtraParams["container"] as a
raw string ID and as an object map, rather than using
AnthropicMessageRequest.Container. Verify each conversion promotes the value
into the corresponding AnthropicContainer typed field and removes "container"
from ExtraParams.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 32d86689-b9e6-4e6d-aa1e-b62cec6b0e06
📒 Files selected for processing (2)
core/providers/anthropic/responses.gocore/providers/anthropic/responses_test.go
The merge-base changed after approval.
The merge-base changed after approval.
c556c60 to
244a01d
Compare
…iew maintenance (maximhq#5693) * feat: support matview_refresh_interval "off" to disable logstore matview maintenance The materialized views back only the dashboard UI. Deployments that run Bifrost headless behind their own observability stack pay the REFRESH MATERIALIZED VIEW CONCURRENTLY cost for views nothing reads, and the 5s floor means the interval alone cannot turn maintenance off. With "off" (or a non-positive duration) the logs store skips view creation, the initial refresh, and the periodic refresher entirely. matViewsReady stays false, so dashboard queries fall back to the raw tables, and the runtime self-heal path cannot re-arm maintenance since it only triggers from matview-path queries. * fix: guard matview self-heal when maintenance is disabled Review follow-up: carry the resolved disabled state onto the store so triggerMatViewSelfHeal cannot recreate views the configuration says must not exist, and make the schema/docs explicit that a zero duration also disables (positive sub-5s values still clamp up).
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Bug Fixes** * Improved token parameter compatibility handling to preserve alternative formats when the primary option is unsupported. * **Chores** * Version updated to 2.0.0. * Enhanced load testing configuration for more reliable builds. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Briefly explain the purpose of this PR and the problem it solves. - What was changed and why - Any notable design decisions or trade-offs - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs Describe the steps to validate this change. Include commands and expected outcomes. ```sh go version go test ./... cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` If adding new configs or environment variables, document them here. If UI changes, add before/after screenshots or short clips. - [ ] Yes - [ ] No If yes, describe impact and migration instructions. Link related issues and discussions. Example: Closes maximhq#123 Note any security implications (auth, secrets, PII, sandboxing, etc.). - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary Adds a `THIRD_PARTY_NOTICES.md` file to formally document third-party components used in Bifrost that carry license terms requiring explicit attribution — specifically MPL-2.0 licensed dependencies and embedded source code derived from external projects. ## Changes - Introduces `THIRD_PARTY_NOTICES.md` to attribute: - Embedded source code in `framework/migrator/migrator.go` derived from `go-gormigrate/gormigrate` (MIT) - Go binary dependencies carrying MPL-2.0 terms: `github.com/cyphar/filepath-securejoin` and `github.com/hashicorp/go-version` - npm build-time devDependencies carrying MPL-2.0 terms: `lightningcss` (never shipped to end users) and `dompurify` (Apache-2.0 option elected) - All MPL-2.0 components are used unmodified and combined as a "Larger Work" per MPL-2.0 Section 3.3; no Bifrost source files are themselves MPL-licensed. ## 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 No functional changes — review the file contents to confirm accuracy of license attributions against the listed upstream repositories. ## Breaking changes - [ ] Yes - [x] No ## Related issues N/A ## Security considerations This change has no security implications. It is a legal/compliance attribution document only. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## 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
## 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
…aximhq#5759) ## Summary Closes a race-condition security gap where an unauthenticated network caller could reach a freshly deployed, not-yet-configured Bifrost instance and create the first admin account before the real operator does. Previously, `PUT /api/config` was intentionally open when no admin account existed (zero-config UX), but this left a window of exposure on any publicly reachable host. The fix introduces a one-time **setup token** — generated in-memory at startup when no admin account is configured, printed to the server's startup logs, and required alongside the username/password when creating the first admin account. The token is never persisted, is regenerated on every restart until an admin account exists, and is permanently invalidated once the first admin account is created. ## Changes - **Bootstrap token generation (`middlewares.go`):** `InitAuthMiddleware` generates a UUID setup token via `atomic.Pointer[string]` when no admin account is configured, logs it prominently to stdout, and exposes `CheckBootstrapToken` (constant-time comparison) and `ClearBootstrapToken` methods. - **Token validation in the config handler (`config.go`):** `updateConfig` now calls `ValidateSetupToken` before allowing the first admin account to be created. Returns HTTP 403 if the token is missing or wrong. - **Token cleared on first admin account creation (`server.go`):** `UpdateAuthConfig` calls `ClearBootstrapToken` after successfully persisting the first admin account, permanently closing the gate. - **`setup_token`** **field added to** **`UpdateConfigRequest`:** The field is accepted in the request body but never persisted or returned by `GET /api/config`. - **UI (`securityView.tsx`):** When no `auth_config` exists server-side (`isFirstTimeSetup`), a **Setup token** input field is shown below the password field. The token is validated client-side before submission and cleared from state after a successful save. - **TypeScript types (`config.ts`):** `setup_token?: string` added to `BifrostConfig`. - **OpenAPI schema (`config.yaml`):** `setup_token` documented on `UpdateConfigRequest`. - **Docs:** A `<Warning>` block added to `security-best-practices.mdx` and a `<Note>` added to `setting-up-auth.mdx` explaining the setup token flow, where to find it, and that it only applies once. - **Tests (`middlewares_test.go`):** Two new test cases cover the no-token-generated (pass-through) case and the validate-then-clear lifecycle. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [x] Docs ## How to test **Manual flow:** 1. Start a fresh Bifrost instance with no existing admin account. 2. Check startup logs for the block beginning `No admin account is configured for this Bifrost instance yet.` and copy the setup token. 3. Open the dashboard → Security Settings. Confirm the **Setup token** field appears below the password field. 4. Attempt to save with auth enabled but without the setup token — expect a toast error. 5. Paste the correct token and save — expect success and the Setup token field to disappear on reload. 6. Confirm that `PUT /api/config` without the token returns HTTP 403 while no admin account exists. 7. Restart the server before completing setup and confirm a new token is printed. ```sh # Core/Transports go test ./transports/bifrost-http/handlers/... # UI cd ui pnpm i pnpm build ``` ## Breaking changes - [x] Yes - [ ] No Any automation or scripts that call `PUT /api/config` to create the first admin account on a fresh instance must now include `setup_token` in the request body. The token is available in the server's startup logs. Instances that already have an admin account configured are unaffected — the field is ignored once an admin account exists. ## Security considerations - The setup token is generated with `uuid.NewString()` (crypto-random UUID), stored only in process memory, and compared with `crypto/subtle.ConstantTimeCompare` to prevent timing attacks. - The token is never written to disk, never returned by any API endpoint, and is permanently invalidated after first use. - Operators must have access to the process's stdout/log stream (`docker logs`, `kubectl logs`, or terminal) to retrieve the token, which is the same access level required to operate the host — this is the intended trust boundary. ## Checklist - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicablecg
## 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
Loading a custom plugin `path` causes native code (a `.so`) to be `dlopen()`'d directly into the gateway process. Previously, this was allowed even when dashboard authentication was disabled or unconfigured — meaning any caller who could reach the management API could inject arbitrary native code. This PR closes that gap by requiring a genuinely authenticated admin session for any create or update operation that sets a non-builtin plugin `path`, and separately hardens the plugin downloader against SSRF. - Added `BifrostContextKeyAuthBypassed` context key, set by the auth middleware exclusively when a request is let through because dashboard auth is disabled/unconfigured (distinct from `IsLocalAdminContextKey`, which is also set on real authenticated sessions). - `createPlugin` and `updatePlugin` handlers now check `BifrostContextKeyAuthBypassed` and return `403` before any DB write when a non-builtin `path` is supplied without genuine authentication. - Replaced the `fasthttp`-based plugin downloader with a `net/http` client backed by `network.SSRFSafeDialContext`, matching the SSRF hardening already applied to `core/providers/utils.FetchAndEncodeURL`. The new client: rejects non-`http`/`https` schemes before any network call, refuses connections to loopback, private, CGNAT, link-local, and unspecified addresses (including IPv4-in-IPv6 transition addresses) at dial time (not just DNS lookup time, so DNS rebinding doesn't bypass it), applies the same IP check to redirect targets, caps redirect depth at 5, and limits response body reads to 200 MB. - Tests for `DownloadPlugin` now use a `useNonSSRFGuardedClient` helper that swaps in a plain dialer for the duration of each test (since `httptest` servers bind to loopback, which the production dialer correctly blocks). A new `TestDownloadPlugin_BlocksSSRFToLoopback` test verifies the production guard is active by default, and `TestDownloadPlugin_RejectsNonHTTPScheme` verifies `file://` and similar schemes are rejected before any network call. - New handler tests cover all four cases: create with bypassed auth (expect 403, no DB write), create with real auth (expect 201, path stored), update with bypassed auth (expect 403, no DB write), and the existing config-merge behaviour. - OpenAPI docs and the plugin sequencing guide updated to document the 403 response and the authentication requirement for `path`. - Dependency bumps: `aws-sdk-go-v2/config` → v1.32.14, `aws-sdk-go-v2/service/s3` → v1.99.0, `aws-sdk-go-v2/internal/ini` → v1.8.6, `buger/jsonparser` → v1.2.0. - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [x] Chore/CI > This is primarily a security hardening change. - [x] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [x] Docs ```sh go test ./... go test ./transports/bifrost-http/handlers/... -run TestCreatePlugin_RejectsCustomPathWhenAuthBypassed go test ./transports/bifrost-http/handlers/... -run TestCreatePlugin_AllowsCustomPathWhenNotBypassed go test ./transports/bifrost-http/handlers/... -run TestUpdatePlugin_RejectsCustomPathWhenAuthBypassed go test ./framework/plugins/... -run TestDownloadPlugin_BlocksSSRFToLoopback go test ./framework/plugins/... -run TestDownloadPlugin_RejectsNonHTTPScheme ``` To manually verify the 403 behaviour: start the gateway with no dashboard auth configured, then attempt `POST /api/plugins` with a `path` field pointing to a `.so`. The response should be `403` with a message instructing the operator to enable dashboard authentication first. - [x] Yes - [ ] No Operators running with dashboard authentication disabled who were previously able to create or update custom plugin paths via the API will now receive a `403`. To restore the capability, enable dashboard authentication and authenticate before calling those endpoints. - Closes an unauthenticated native code injection vector: without this change, any network-reachable caller could `dlopen()` an attacker-controlled `.so` into the gateway process when dashboard auth was off. - The SSRF fix on the plugin downloader prevents a crafted plugin URL from causing the gateway to fetch from internal/metadata endpoints (e.g. cloud IMDS). The guard runs at dial time, not DNS resolution time, so DNS rebinding attacks do not bypass it. - `BifrostContextKeyAuthBypassed` is intentionally separate from `IsLocalAdminContextKey` so that future handlers gating other high-risk operations can use the same signal without ambiguity. - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
## Summary Resolves merge conflicts in `core/go.sum` that were left over from merging the path normalization auth bypass fix (maximhq#5763). ## Changes - Removed leftover `<<<<<<< HEAD`, `=======`, and `>>>>>>> e0057ff` conflict markers from `core/go.sum` - Retained the correct `go.mod` hash lines for `aws-sdk-go-v2/config`, `aws-sdk-go-v2/internal/ini`, and `aws-sdk-go-v2/service/s3` that were dropped during the conflict ## 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 cd core go mod verify go test ./... ``` ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes maximhq#5763 ## Security considerations No security implications. This is a cleanup of unresolved merge conflict markers in the dependency lockfile. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary Adds a dedicated **MCP Guardrails** section to the guardrails documentation, explaining how guardrails apply at the tool-execution boundary for MCP targets, including phase behavior, UI field descriptions, and provider compatibility notes. ## Changes - Replaced the brief inline mention of MCP rule behavior in the Architecture section with a cross-reference link to the new dedicated section. - Added a new `## MCP Guardrails` section covering: - How `input`, `output`, and `both` phases apply at the tool-execution boundary. - A table describing the flow and block behavior for each phase. - Guidance on selecting MCP clients, tools, and tool arguments when creating rules. - A note clarifying that all supported guardrail providers work with MCP rules and that redaction support follows the same provider constraints as LLM rules. - Cross-references to existing sections for rule configuration and CEL expression examples. - Added a screenshot (`ui-mcp-guardrail-rule.png`) showing the MCP guardrail rule editor in the UI. ## 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 rendered guardrails documentation page and verify: - The Architecture section links to `#mcp-guardrails` instead of containing inline MCP text. - The new MCP Guardrails section renders correctly with the phase table, screenshot, and note. - The screenshot image loads without errors. ## Screenshots/Recordings The new section includes a screenshot of the MCP guardrail rule editor (`ui-mcp-guardrail-rule.png`). ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations None. Documentation-only change. ## 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
…#5962) Fix the budget override **Valid until** preview for calendar-aligned budgets. The UI now calculates expiry from the current UTC calendar-period boundary instead of directly from a preserved, mid-period `last_reset` timestamp. For example, a monthly calendar-aligned budget with `last_reset = Aug 3` should expire at the next monthly boundary on Sep 1, not Sep 3: ```text Aug 1 00:00 UTC Aug 3 Sep 1 00:00 UTC |--------------------------|--------------------------| calendar period start last_reset valid until Old UI: Aug 3 + 1 month -> Sep 3 08:59 UTC New UI: Aug 1 + 1 month -> Sep 1 00:00 UTC ``` The backend override cycle was already correct; this only fixes the date displayed by the UI. - Added a helper that snaps calendar-aligned reset timestamps to their current UTC period boundary: - Day: 00:00 UTC on the current day - Week: Monday at 00:00 UTC - Month: first day of the month at 00:00 UTC - Year: January 1 at 00:00 UTC - Applied that boundary before adding override cycles in `getBudgetOverrideValidUntil`. - Kept rolling and sub-day calculations unchanged. - Added regression coverage for daily, weekly, monthly, and yearly calendar-aligned overrides. The calculation now follows this flow: ```text preserved last_reset Aug 3 08:59 | v snap to calendar period Aug 1 00:00 | v add one monthly cycle Sep 1 00:00 ``` - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs Run the focused utility tests: ```sh cd ui npx vitest run lib/utils/governance.test.ts ``` Expected outcome: ```text Test Files 1 passed Tests 5 passed ``` Run the TypeScript typecheck: ```sh cd ui npm run typecheck ``` Expected outcome: the command completes without TypeScript errors. The regression test verifies these representative cases: ```text Monthly: Aug 3 08:59 + 1 cycle -> Sep 1 00:00 UTC Weekly: Wed Aug 5 + 1 cycle -> Mon Aug 10 00:00 UTC Daily: Aug 3 08:59 + 1 cycle -> Aug 4 00:00 UTC Yearly: Aug 3 2026 + 1 cycle -> Jan 1 2027 00:00 UTC ``` No new configuration or environment variables are introduced. Not included. This changes the date calculation behind the existing **Valid until** field without changing the UI layout. Before: ```text Valid until: Sep 3, 2026 ``` After: ```text Valid until: Sep 1, 2026 ``` - [ ] Yes - [x] No No linked issue. No security implications. This change only adjusts a client-side date preview and does not affect authentication, authorization, secrets, PII, or backend enforcement. - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
## Summary Read-only metadata requests (e.g. list models) should not be blocked by exhausted provider budgets or rate limits, since they consume no quota. Previously, the `skipBudgetsAndRateLimits` flag only bypassed VK-level and hierarchy checks, but provider-level budget and rate-limit evaluation still ran unconditionally. This PR ensures that when the skip flag is set, provider/model governance checks are also bypassed, while VK identity enforcement (existence, active status) remains intact. ## Changes - Moved the `skipBudgetsAndRateLimits` flag read to before the `EvaluateModelAndProviderRequest` call so it can gate that evaluation. - When `skipBudgetsAndRateLimits` is `true`, `EvaluateModelAndProviderRequest` is skipped entirely and the result defaults to `DecisionAllow`, preventing exhausted provider budgets or rate limits from blocking metadata-only calls. - Added two tests pinning this behavior: one for an exhausted provider budget (expects 402 on inference, allow on list models) and one for an exhausted provider rate limit (expects 429 on inference, allow on list models). ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./plugins/governance/... -run TestGovernancePlugin_EvaluateGovernanceRequest_SkipFlagBypasses ``` Expected: both `TestGovernancePlugin_EvaluateGovernanceRequest_SkipFlagBypassesProviderBudget` and `TestGovernancePlugin_EvaluateGovernanceRequest_SkipFlagBypassesProviderRateLimit` pass, confirming that list-models requests are allowed even when the provider budget is exhausted (402) or rate limit is maxed (429). ## Screenshots/Recordings N/A ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations The skip flag only bypasses budget and rate-limit enforcement. VK identity checks (existence and active status) continue to run regardless, so unauthenticated or inactive keys cannot exploit this path to enumerate models. ## 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
…start_month`, Entra ID OBO gotchas, and clarify token-exchange app registration guidance (maximhq#6062) ## Summary Adds quarterly (`1Q`) budget reset windows with configurable fiscal-year alignment, and expands the MCP token-exchange documentation to clarify application registration flexibility and document Microsoft Entra ID OBO setup gotchas discovered during real end-to-end testing. ## Changes - **Quarterly budget resets (`1Q`):** Added `1Q` as a valid `reset_duration` enum value for budgets across the OpenAPI spec. Budgets now accept an optional `reset_config` object containing `quarter_start_month` (1–12), which controls which month begins Q1. Omitting it defaults to January. Only valid when `reset_duration` is `1Q`; the API rejects `reset_config` on any other window. Rate limit `reset_duration` descriptions are now distinct from budget descriptions to reflect that `1Q` and `reset_config` are budget-only. - **Token exchange prerequisites clarification:** Relaxed the wording around requiring a dedicated exchange application — it is now recommended rather than required, with an explanation of why isolation is still a good idea. Clarified that Bifrost imposes no constraint beyond what is configured per MCP client, and that the exchange app's credentials are never compared against SCIM provider credentials. - **Microsoft Entra ID OBO troubleshooting section:** Added a new "known setup gotchas" section covering five concrete errors encountered during a real Entra tenant setup: token issuer mismatch due to `requestedAccessTokenVersion`, audience mismatch between `api://` URI and bare GUID, using an `id_token` instead of an `access_token` as the OBO assertion, `AcceptMappedClaims` rejection when using the App ID URI, and how to configure the exchange app to expose an API to itself for manual testing. ## 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 Review the rendered token-exchange documentation to confirm the Entra ID gotchas section appears correctly and the prerequisites wording reflects the relaxed constraint. For the OpenAPI spec, validate that: - `1Q` appears as a valid enum value under budget `reset_duration` fields. - `reset_config` with `quarter_start_month` appears as a sibling of `reset_duration` on budget objects only. - Rate limit `reset_duration` descriptions read "Reset duration for a rate limit." and budget ones read "Reset duration for a budget." ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations The token-exchange documentation update clarifies that the exchange application's client ID and secret are read solely from the MCP client's own `token_exchange` config and are never compared against or combined with SCIM provider credentials, which is relevant for operators reasoning about credential isolation. ## 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
…ification` state copy to verify dialog (maximhq#6063) ## Summary Improves the MCP token exchange verification flow by distinguishing between first-time verification and re-verification states, and moves the setup screenshot to appear before the setup steps in the documentation. ## Changes - The "Re-verify as me" dialog now shows context-aware title and description text: clients in `pending_verification` state display "Verify as me" and "Establish Bifrost's discovery credential..." instead of the renewal-focused copy - The info box body text within the verify dialog similarly adapts between "establish" and "renew" language, and omits the "you only need this if the credential badge shows it's expired" note for first-time verifications since that guidance is irrelevant at initial setup - Adds three new UI screenshots (`ui-mcp-token-exchange-setup.png`, `ui-mcp-token-exchange-verify.png`, `ui-mcp-token-exchange-repair.png`) to the docs media directory - Moves the token exchange setup screenshot above the numbered steps in `token-exchange.mdx` so readers see the UI before following the instructions ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [x] Docs ## How to test 1. Create a new MCP client with token exchange auth — the verify dialog should show "Verify as me" and "Establish..." copy 2. After the client is active, open the verify dialog again — it should show "Re-verify as me" and "Renew..." copy including the expiry guidance note 3. Confirm the token exchange setup page in the docs renders the screenshot above the numbered steps ## Screenshots/Recordings Three new screenshots added under `docs/media/`: - `ui-mcp-token-exchange-setup.png` — the MCP client creation form with token exchange fields - `ui-mcp-token-exchange-verify.png` — the verify dialog for an active client - `ui-mcp-token-exchange-repair.png` — the verify dialog for a client in a degraded/expired state ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No security implications. Changes are limited to UI copy and documentation. ## 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
…O login app credentials to be reused for providers like Microsoft Entra ID that require it (maximhq#6068) ## Summary Adds `use_idp_credentials` support to the token exchange auth flow, allowing MCP clients to perform token exchanges using the SSO login application's own client ID and secret rather than requiring a dedicated exchange application. This is necessary for Microsoft Entra ID, whose on-behalf-of grant only accepts assertions audienced to the exchanging application — since Bifrost's SSO login flow always requests a token self-audienced to the SSO application, a separate exchange application can never receive a usable assertion on Entra. ## Changes - Added `UseIdPCredentials bool` to `MCPTokenExchangeConfig` in the schema, with `client_id` now optional when this flag is true - Added `IdPClientID` and `IdPClientSecret` fields to `TokenExchangeIdP` so the SSO application's own credentials can be threaded through to the exchange call - Updated `exchangeClientCredentials` to accept the `TokenExchangeIdP` and return the IdP's own credentials when `UseIdPCredentials` is set, falling back to the per-client block otherwise - Updated `validateExchangeConfig` and `validateMCPClientConfig` to skip the `client_id` requirement when `UseIdPCredentials` is true, with updated error messages reflecting the new option - Updated `rotateMCPTokenExchangeConfigFromFile` to propagate `UseIdPCredentials` from file-based config (one-way: setting true is supported; clearing it requires the update API) - Updated `DiffersFrom` on `MCPTokenExchangeConfig` to include `UseIdPCredentials` in change detection - Added a two-option radio picker ("Dedicated application" / "Identity provider application") to `TokenExchangeFields`, which hides the client ID and client secret fields when the IdP credentials option is selected - Detected the enabled SCIM provider name in both the create form and the edit sheet to surface a warning when the connected IdP is Microsoft Entra ID and the dedicated application option is selected - Removed the static descriptive paragraph from the token exchange section of the create form in favor of the contextual per-option descriptions on the picker - Updated `MCPTokenExchangeConfig` TypeScript type and Zod schema to reflect `client_id` as optional and add `use_idp_credentials` ## 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. Configure a token exchange MCP client with `use_idp_credentials: true` and no `client_id` — creation and validation should succeed. 2. Configure a token exchange MCP client without `use_idp_credentials` and no `client_id` — creation should fail with an error indicating `client_id` is required unless `use_idp_credentials` is set. 3. With a Microsoft Entra ID SCIM provider enabled, open the MCP client create form and select token exchange auth — the "Dedicated application" option should show a warning about Entra requiring the identity provider application option. 4. Toggle to "Identity provider application" — the client ID and client secret fields should disappear and the warning should clear. 5. Submit with "Identity provider application" selected — the exchange should use the SSO application's credentials. 6. Verify that file-based config with `use_idp_credentials: true` propagates correctly and that setting it to `false` in a file has no effect (requires the update API to clear). ```sh go test ./core/mcp/... ./framework/oauth2/... ./transports/bifrost-http/... cd ui pnpm i pnpm build pnpm test ``` ## Screenshots/Recordings Before: Token exchange form always showed client ID and client secret fields with no option to reuse SSO credentials. After: A "Exchange application" radio picker appears above the credential fields. Selecting "Identity provider application" hides the client ID and client secret fields. When Entra is detected as the IdP, a warning is shown when "Dedicated application" is selected. ## Breaking changes - [ ] Yes - [x] No `client_id` is now optional in the schema when `use_idp_credentials` is true. Existing configurations without the flag are unaffected. ## Related issues ## Security considerations The SSO application's own client secret is now conditionally used for token exchange requests. These credentials are sourced from the deployment's existing IdP integration and are not stored separately. They are only passed through to the token endpoint and are not logged or exposed to callers. The `use_idp_credentials` path bypasses the per-client credential block entirely, so misconfigured clients cannot inadvertently receive the SSO application's secret. ## 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
…ent_id` optional when set, and document Entra ID requirement with updated prerequisites and gotchas (maximhq#6069) ## Summary Adds `use_idp_credentials` to the `token_exchange` auth type, allowing the token exchange to run as the SSO login application itself rather than a separately registered dedicated exchange application. This is required for Microsoft Entra ID, whose on-behalf-of grant enforces that the assertion's audience matches the exchanging application — a structural constraint that makes a dedicated exchange application impossible to use with Entra. ## Changes - Added `use_idp_credentials: boolean` (default `false`) to `MCPTokenExchangeConfig` across the config schema, OpenAPI spec, and management YAML schema. When `true`, `client_id` and `client_secret` are ignored and the exchange uses the SSO login application's credentials instead. - `client_id` is no longer unconditionally required — the schema now enforces it conditionally: required unless `use_idp_credentials: true` is set. - `authorization_server_url` was added to the transport config schema where it was previously missing. - Updated the token exchange documentation to explain the two exchange application modes, why Entra structurally requires `use_idp_credentials: true` (with a citation to Microsoft's own OBO reference), and how RFC 8693 providers differ from Entra's pre-standard OBO grant. - Reorganized the Microsoft Entra ID known-gotchas list to lead with setting `use_idp_credentials: true` as step 1, since without it no other Entra troubleshooting step is relevant. - Updated the UI setup instructions to reflect the new **Exchange application** selector (Dedicated vs. Identity provider application) and the conditional display of client ID/secret fields. - Added a `use_idp_credentials: false` field to the reference API response example for completeness. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [x] Docs ## How to test Configure an MCP client with `auth_type: token_exchange` against a Microsoft Entra ID tenant: ```json "token_exchange": { "audience": "<entra-resource-app-client-id>", "use_idp_credentials": true } ``` 1. Verify the client reaches `pending_verification` state without a schema validation error (previously would have failed requiring `client_id`). 2. Click **Verify as me** — the exchange should succeed and the client move to `verified`. 3. Confirm that omitting both `use_idp_credentials: true` and `client_id` still fails schema validation. 4. Confirm that a non-Entra provider (Okta, Auth0) still works with `use_idp_credentials: false` and an explicit `client_id`. ## Breaking changes - [x] Yes - [ ] No `client_id` is no longer required at the schema level when `use_idp_credentials: true` is set. Existing configs that supply `client_id` are unaffected. No previously valid configuration becomes invalid. ## Security considerations `use_idp_credentials: true` causes the exchange to use the SSO login application's own credentials, which are shared across all MCP clients configured this way. This is intentional and required for Entra, but means a misconfigured audience could result in tokens being minted for unintended resources using the SSO application's grant. The per-client `audience` field remains the scope boundary. `client_id` and `client_secret` continue to be redacted in API responses and support `env.VAR_NAME`/`vault.path` references. ## 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
## Summary
Adds support for routing AWS Bedrock traffic through interface VPC endpoints (AWS PrivateLink), allowing deployments where Bedrock services are accessed over private networking rather than the public regional endpoints. Each AWS endpoint service Bifrost dials (bedrock-runtime, bedrock, bedrock-mantle, bedrock-agent-runtime, s3) can be independently overridden with a VPC endpoint DNS name per key config.
## Changes
- Introduced `BedrockEndpoints` schema type holding per-service VPC endpoint host overrides for both `BedrockKeyConfig` and `BedrockMantleKeyConfig`.
- Added `resolveBedrockHost` utility that returns the configured VPC endpoint host when set, falling back to the public regional hostname. Mantle is handled as a special case since its public host lives under `api.aws` rather than `amazonaws.com`.
- Added `bedrockEndpoints` helper to safely extract endpoint config from a potentially nil `BedrockKeyConfig`.
- Replaced all hardcoded `fmt.Sprintf("https://bedrock-runtime.%s.amazonaws.com/...")` style URL construction across `bedrock.go`, `mantle.go`, and `bedrockmantle.go` with calls to `resolveBedrockHost`, covering inference, streaming, agent runtime, control plane, S3 file operations, and batch job endpoints.
- Updated `mantleOpenAIURL`, `mantleAnthropicURL`, and `mantleAnthropicCountTokensURL` to accept an `*schemas.BedrockEndpoints` argument so the override propagates through all Mantle call sites.
- Added `NormalizeEndpointHost` to strip scheme and path from a pasted endpoint value, so users can paste a full URL from the AWS console without breaking URL construction.
- Added `bedrockService` typed constants (`bedrockServiceRuntime`, `bedrockServiceControlPlane`, `bedrockServiceMantle`, `bedrockServiceAgentRuntime`, `bedrockServiceS3`) to make service identity explicit and avoid stringly-typed dispatch.
- Persisted `BedrockEndpoints` as encrypted JSON columns (`bedrock_endpoints_json`, `bedrock_mantle_endpoints_json`) in the `config_keys` table, with full `BeforeSave`/`AfterFind` encrypt/decrypt lifecycle and a database migration.
- Exposed VPC endpoint hosts in the redacted config view (they are network addresses, not credentials).
- Added a collapsible **VPC Endpoints** section to the Bedrock and Bedrock Mantle key forms in the UI, with per-service fields and a validation rule requiring a DNS name (containing a dot) rather than a bare endpoint ID.
- Updated `config.schema.json` with the `endpoints` object for both Bedrock and Bedrock Mantle key configs.
- Added `vpcendpoints_test.go` covering default host resolution, per-service override isolation, host normalization edge cases, and SigV4 signing correctness with a VPC endpoint host. Updated existing tests to pass `nil` endpoints where the new parameter was added.
## 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 test ./core/providers/bedrock/... ./core/providers/bedrockmantle/... ./framework/configstore/...
# UI
cd ui
pnpm i
pnpm build
```
To validate end-to-end, configure a Bedrock key with an `endpoints.runtime` value set to a VPC endpoint DNS name and confirm inference requests are directed to that host. Confirm that omitting the field falls back to the standard `bedrock-runtime.{region}.amazonaws.com` host. Confirm SigV4 signing still uses the configured region regardless of which host is dialled.
**New config fields (`BedrockKeyConfig.endpoints`):**
| Field | AWS endpoint service | Default public host |
|---|---|---|
| `runtime` | `bedrock-runtime` | `bedrock-runtime.{region}.amazonaws.com` |
| `control_plane` | `bedrock` | `bedrock.{region}.amazonaws.com` |
| `mantle` | `bedrock-mantle` | `bedrock-mantle.{region}.api.aws` |
| `agent_runtime` | `bedrock-agent-runtime` | `bedrock-agent-runtime.{region}.amazonaws.com` |
| `s3` | `s3` | `s3.{region}.amazonaws.com` |
Values accept the full DNS name from the VPC console (e.g. `vpce-0abc123-x1y2z3.bedrock-runtime.eu-west-2.vpce.amazonaws.com`), a URL with scheme, or a URL with a trailing path — all are normalized to a bare host.
## Breaking changes
- [ ] Yes
- [x] No
## Security considerations
VPC endpoint hosts are stored encrypted at rest alongside other key config. They are surfaced in plaintext in the redacted config view because they are network addresses rather than credentials. SigV4 signing is unaffected: the credential scope continues to use the configured region, not the hostname, so a VPC endpoint host does not alter the signature scope.
## Checklist
- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
* docs: expand deployment platform guides * docs: revert changes to k8s file * Update terraform/modules/bifrost/gcp/services/cloud-run/main.tf Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Raggav Subramani <raggav.subramani@gmail.com> * docs: address deployment guide review comments * docs: clarify enterprise deployment storage requirements * docs: verify AKS PostgreSQL encoding * docs: address deployment guide review feedback * docs: fix Kubernetes deployment guides * docs: clarify EKS Auto Mode storage setup --------- Signed-off-by: Raggav Subramani <raggav.subramani@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Suresh Chaudhary <hello@suresh.im>
…O instead of replacing it, and document the `.default`-scope replacement behavior as a new gotcha (maximhq#6078) ## Summary When using Microsoft Entra ID's On-Behalf-Of flow, configuring `offline_access` as a scope was silently dropping resource access entirely. Because the JWT-bearer OBO grant shape has no separate audience parameter, sending `scope=offline_access` alone requests nothing but a refresh token with no resource attached. This fixes that by combining `offline_access` with the audience-derived `.default` scope (`<audience>/.default offline_access`) rather than replacing it — matching the one exception Microsoft's own OBO documentation carves out. Any other configured scope still fully replaces the default, since Entra forbids combining `.default` with arbitrary delegated scopes (AADSTS70011). ## Changes - **`scopeParam` logic (`framework/oauth2/tokenexchange.go`):** When `defaultToAudience` is set and the configured scopes are exactly `["offline_access"]`, the function now prepends the audience-derived default (`<audience>/.default`) rather than discarding it. All other configured scopes continue to fully replace the default, preserving Entra's restriction on combining `.default` with custom delegated scopes. - **Tests (`framework/oauth2/tokenexchange_test.go`):** Two new tests pin the two cases — `offline_access` alone combines with the default; any other scope replaces it entirely. - **UI (`mcpClientForm.tsx`, `mcpClientSheet.tsx`):** The Audience tooltip and Scopes helper text are now Entra-aware. When the configured identity provider is Entra ID, the Audience field explains that the value must be the resource app's bare GUID (not the `api://...` Application ID URI), and the Scopes field explains that `offline_access` is the only scope that combines with default resource access rather than replacing it. - **Docs (`docs/mcp/auth/token-exchange.mdx`):** Added a sixth Entra gotcha covering the `.default`-plus-scope restriction and Bifrost's `offline_access` exception. Updated the Audience field description in both the UI walkthrough and the YAML reference table to call out the bare GUID requirement. Expanded the Scopes field description in both places to explain the replacement-vs-combination behavior. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [x] UI (React) - [x] Docs ## How to test ```sh # Core/Transports go test ./framework/oauth2/... ``` Verify: - `TestGetExchangedAccessTokenJWTBearerOBOOfflineAccessCombinesWithDefault` passes and the captured `scope` form value is `api://client-1/.default offline_access`. - `TestGetExchangedAccessTokenJWTBearerOBOCustomScopeReplacesDefault` passes and the captured `scope` form value is `api://client-1/access_as_user` with no `.default` appended. For the UI, configure an Entra ID identity provider and open the MCP client create/edit form. Confirm: - The Audience tooltip reads "bare GUID, not the `api://...` Application ID URI". - The Scopes helper text includes the `offline_access`-only combination note. - Non-Entra providers show the original generic tooltip and helper text. ## Breaking changes - [ ] Yes - [x] No ## Security considerations The fix ensures that `offline_access`-only scope configurations actually request access to the intended resource rather than issuing a refresh token scoped to nothing. Without this, a token exchange that appeared to succeed would return a credential with no resource access, potentially causing silent authorization failures downstream.
…ngine, overrides, and docs (maximhq#6079) ## Summary Adds support for a `cost_per_request` flat fee field in the pricing system. This allows a fixed surcharge to be billed once per request, additive on top of any existing usage-based costs (tokens, audio seconds, images, etc.), regardless of request type. ## Changes - Added `CostPerRequest` field to `TableModelPricing`, `Options`, and `PricingEntry` types, with full conversion between them - Added a database migration (`add_cost_per_request_pricing_column`) to introduce the new column - Updated `computeCostFromInput` to apply the flat per-request fee after computing usage-based cost for all supported request types - Included `cost_per_request` in the pricing sync update columns and the `patchPricing` override path - Exposed `cost_per_request` in the model info pricing output (`pricing.Request`) - Added `cost_per_request` to the OpenAPI schema and governance YAML with a `minimum: 0` constraint - Added the field to the custom pricing override UI, available across chat, embedding, rerank, audio, image, video, and OCR request type groups - Added `cost_per_request` to the `PricingOverridePatch` TypeScript interface - Updated the model catalog architecture docs and custom pricing provider docs to document the new field - Added unit tests covering flat fee billing (`TestCalculateCost_ChatCompletion_CostPerRequest`) and override patching (`TestPatchPricing_CostPerRequest`) ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [x] 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 ``` Set `cost_per_request` on a custom pricing override for any model and verify that the billed cost equals the usage-based cost plus the flat fee. For example, with `input_cost_per_token=0.000005`, `output_cost_per_token=0.000015`, and `cost_per_request=0.01`, a request with 10,000 prompt tokens and 2,000 completion tokens should produce a total cost of `$0.09`. ## Screenshots/Recordings The "Flat fee / request" field will appear in the custom pricing override sheet for chat, embedding, rerank, audio, image, video, and OCR request types. ## Breaking changes - [x] No ## Related issues ## Security considerations No auth, secrets, or PII implications. The new field is a non-negative float and is validated with `minimum: 0` in the schema. ## 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
…ld wiring across DB, cost engine, API, docs, and UI (maximhq#6080) ## Summary Adds a reusable Claude skill (`add-pricing-field`) that automates wiring a new model-pricing field end-to-end through Bifrost's pricing engine. The skill ensures no step is silently skipped — particularly the `pricingSyncUpdateColumns` upsert list in `rdb.go`, which is the most common source of pricing fields that appear to work but quietly revert to null after the first 24h datasheet resync. ## Changes - Introduces `.claude/skills/add-pricing-field/SKILL.md`, a structured 12-step workflow invokable via `/add-pricing-field <field_name>` that covers: - Semantic classification of the new field (additive surcharge, threshold tier, or rate substitute) before any code is written - `Options` struct definition and bidirectional `convertEntry↔TablePricing` mappings in `types.go` - `TableModelPricing` column addition and idempotent migration registration in `migrations.go` - `pricingSyncUpdateColumns` update in `rdb.go` (explicitly called out as the most-missed step) - `patchPricing` entry in `overrides.go` for custom pricing override support - Cost calculation wiring in the appropriate `compute*Cost` function in `cost.go` - Conditional public API surface update in `modelinfo.go` / `schemas/models.go` - OpenAPI YAML property addition and bundle regeneration (never hand-editing `openapi.json`) - MDX documentation row in `custom-pricing.mdx` - UI type (`PricingOverridePatch`) and form entry (`PRICING_FIELDS`) updates - Cost and override unit tests - A repo-wide grep probe against a known-fully-wired sibling field to catch any enumerated locations not covered by the explicit checklist - Design decision: the skill asks for field semantics before writing code, because silently guessing billing logic on a money field is the one mistake not easily caught by tests. It also enforces that the public `Pricing` struct in `core/schemas/models.go` is not widened without explicit user intent. ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [x] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [x] Docs ## How to test The skill is invoked interactively via Claude: ```sh /add-pricing-field cost_per_request # or /add-pricing-field # (skill will prompt for the field name and semantics) ``` After the skill runs, validate the output with: ```sh # Go build and tests cd framework && go build ./... && go test ./modelcatalog/... ./configstore/... cd ../transports && go build ./... # UI type check cd ../ui && ./node_modules/.bin/tsc --noEmit -p tsconfig.json 2>&1 | grep -i "pricingOverrideSheet\|governance.ts" # OpenAPI bundle regeneration check cd docs/openapi && python3 bundle.py git diff --stat docs/openapi/openapi.json ``` ## Screenshots/Recordings N/A — no UI changes in this PR. ## Breaking changes - [x] No ## Related issues N/A ## Security considerations None. The skill operates on pricing metadata fields (cost rates) with no auth, secrets, or PII implications. The explicit rule against widening the public `Pricing` struct without user confirmation prevents unintended API surface expansion. ## Checklist - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary Improves the placeholder messages shown in the model multiselect dropdown to better reflect the actual state of the component, including distinguishing between load errors, empty results, and missing provider selection. ## Changes - Tracks `isFetching` and `isError` states from both `useLazyGetModelsQuery` and `useLazyGetBaseModelsQuery` - Introduces a `modelLoadError` flag that is true when a fetch has completed with an error (and is not currently re-fetching) - Replaces generic placeholder strings with context-aware messages: - On error: `"Couldn't load models."` - On empty results with a provider selected: `"No models available for this provider."` - On empty results with no provider: `"Select a provider first."` - On empty results when loading on empty is enabled: `"No models available."` - When no results match a search: `"No matching models."` - Adds a trailing newline to the end of the 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 1. Open a form that includes the model multiselect component. 2. Select a provider and observe the placeholder before typing — it should read `"No models available for this provider."` if no models are returned. 3. Clear the provider selection and confirm the placeholder reads `"Select a provider first."` 4. Simulate a network error (e.g., disable the API or use devtools to block the request) and confirm the placeholder reads `"Couldn't load models."` 5. Type a search term that returns no results and confirm the placeholder reads `"No matching models."` ```sh cd ui pnpm i || npm i pnpm build || npm run build ``` ## Screenshots/Recordings Before: - `noResultsFoundPlaceholder`: `"No models found"` - `emptyResultPlaceholder`: `"Start typing to search models..."` / `"Please select a provider first"` After: - `noResultsFoundPlaceholder`: `"No matching models."` / `"Couldn't load models."` - `emptyResultPlaceholder`: `"Couldn't load models."` / `"No models available for this provider."` / `"No models available."` / `"Select a provider first."` ## 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
…hq#6055) ## Summary Adds a `CatalogPricingOverrides` API to the model catalog's pricing override system, enabling the management UI to display which pricing overrides apply to a given model/provider row and which are present informationally (e.g. virtual-key or user-scoped overrides that can't be evaluated without a request context). ## Changes - Introduced `CatalogPricingOverrides` struct with two distinct fields: `AppliedID`/`AppliedPatch` (the winning override under global/provider scopes only) and `Matching` (all overrides touching the model+provider, sorted most-specific-first, for informational display). - Refactored `customPricingData.resolve` into a thin wrapper over a new `resolveEntry` method, which returns the winning `customPricingEntry` directly so callers can recover the override's identity without duplicating the precedence walk. - Added `matchesCatalogProvider` and `matchesModel` helpers on `customPricingEntry` to support catalog-context filtering, where virtual-key/user/provider-key scopes have no runtime identifiers but should still surface informationally. Provider-key-scoped entries carry no `provider_id` and always pass the provider filter. - Added `catalogScopeRank` to order scope kinds most-specific-first for display, independent of runtime identifiers. - Exposed `Store.CatalogPricingOverrides` and `ModelCatalog.GetCatalogPricingOverrides` as the public entry points. - Re-exported `CatalogPricingOverrides` from the `modelcatalog` package via the existing type alias block. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./framework/modelcatalog/... ./framework/modelcatalog/datasheet/... ``` Key scenarios covered by the new tests: - Provider-scoped override beats global-scoped override (`TestCatalogPricingOverrides_ProviderBeatsGlobal`) - Overrides for a different provider are excluded entirely (`TestCatalogPricingOverrides_IgnoresMismatchedProvider`) - Virtual-key, user, and provider-key scoped overrides appear in `Matching` but never in `AppliedID` (`TestCatalogPricingOverrides_NonGlobalScopesAreInformationalOnly`) - Wildcard longest-prefix wins in both `AppliedID` and `Matching` ordering (`TestCatalogPricingOverrides_WildcardLongestPrefixWins`) - Mode filtering applies to `AppliedID` resolution but not to `Matching` listing (`TestCatalogPricingOverrides_ModeFilteringAppliesToWinnerOnly`) - Empty/nil override store returns a zero-value result (`TestCatalogPricingOverrides_EmptyStore`) ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No new auth surfaces or secrets handling. The new method reads from the existing in-memory override store under the existing read lock (`overridesMu.RLock`). ## 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 Exposes pricing override information in the `listModelDetails` API response so the UI can display which models have negotiated or custom rates applied, and strike through only the specific cost fields that differ from the catalog baseline. ## Changes - Added `OverriddenPricing`, `AppliedOverrideID`, and `PricingOverrideIDs` fields to `ModelDetailsResponse`. `OverriddenPricing` carries post-override values only for fields the applied override actually changes; unaffected fields are omitted so the client knows exactly which prices to strike through. - Added `ModelOverriddenPricing` struct holding the four displayed cost fields (`input_cost_per_token`, `output_cost_per_token`, `cache_creation_input_token_cost`, `cache_read_input_token_cost`) as nullable pointers. - Added `ModelPricingOverrideSummary` struct and a top-level `PricingOverrides` map on `ListModelDetailsResponse`. Overrides are deduplicated at the response level rather than inlined per row — a single wildcard override matching every model is serialized once regardless of page size. - Only global and provider-scoped overrides populate `OverriddenPricing`/`AppliedOverrideID`; virtual-key, user, and provider-key scoped overrides appear in `PricingOverrideIDs` for informational display only. - A patch that sets a cost field to its existing catalog value is not treated as an override (no strike-through for identical numbers). - Override resolution uses the model's catalog pricing mode (defaulting to `"chat"`) so an override scoped to a different mode never affects the displayed row. - Added `buildOverriddenPricing`, `changedCost`, and `toPricingOverrideSummary` helpers to keep the handler loop readable. - Added six focused tests covering: global override application without mutating base pricing, deduplication of the override index across multiple models, omission of new fields when no overrides exist, no-op patches that match the base value, overrides on models absent from the catalog, and virtual-key scoped overrides being informational only. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./transports/bifrost-http/handlers/... -run TestListModelDetails ``` Expected: all six new `TestListModelDetails_*` tests pass alongside the existing pricing tests. To validate end-to-end, seed a global pricing override via the config store and call `GET /api/models/details?provider=openai`. Confirm: - `overridden_pricing` appears only on models matched by the override and only for fields with a changed value. - `pricing_overrides` at the response root contains one entry per unique override ID, not one per model row. - Virtual-key scoped overrides appear in `pricing_override_ids` but do not set `overridden_pricing` or `applied_override_id`. ## Breaking changes - [ ] Yes - [x] No New fields are additive and omitempty; existing consumers are unaffected. ## Security considerations Override data returned is read-only metadata already accessible to authenticated callers of the model details endpoint. No new secrets or PII are introduced; virtual key IDs and user IDs present in override summaries are already stored in the config store and gated by existing auth middleware. ## 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
…imhq#6057) ## Summary Pricing field metadata (`PRICING_FIELDS`, `REQUEST_TYPE_GROUPS`, related types and helpers) was previously defined inside `pricingOverrideSheet.tsx`. This meant any read-only consumer (e.g. a model-catalog detail sheet) that needed field labels would have to pull in the full form/mutation dependencies of that component. This PR extracts that metadata into a dedicated `pricingFields.ts` module and re-exports everything from `pricingOverrideSheet.tsx` to preserve backward compatibility for existing importers. ## Changes - Extracted `PRICING_FIELDS`, `REQUEST_TYPE_GROUPS`, `REQUEST_TYPE_OPTIONS`, `getRequestTypeGroup`, `fieldLabelByKey`, `patchKeys`, `PricingFieldKey`, and `FieldErrors` from `pricingOverrideSheet.tsx` into a new `pricingFields.ts` file. - `pricingOverrideSheet.tsx` now re-exports all of the above from `pricingFields.ts`, so no existing import paths break. - `pricingFieldSelector.tsx` updated to import directly from `pricingFields.ts` instead of `pricingOverrideSheet.tsx`. - The motivation is to allow lightweight, read-only consumers to import field labels without incurring the bundle cost of the override sheet's form and mutation logic. ## 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 build || npm run build ``` Verify that the custom pricing overrides sheet still renders correctly, that field selectors display the correct labels, and that no import errors appear in the build output. ## Breaking changes - [x] No ## Security considerations None. This is a pure code organization change with no behavioral differences. ## 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 Surfaces custom pricing overrides in the model catalog UI. When a pricing override is applied to a model, the catalog table and detail sheet now show the original price struck through alongside the effective overridden price, and the detail sheet lists every override that matches the model with its scope, pattern, patch values, and any applicable caveats. ## Changes - Added a new `OverriddenPrice` component that renders a base price normally when no override is active, or shows the original price struck through with the effective price beside it and a tooltip naming the override that produced it. - Replaced all plain `formatTokenPriceCompact` / `formatTokenPriceFull` calls in the catalog table and attribute sheet with `OverriddenPrice`, so overridden fields are visually distinguished without affecting unoverridden rows. - Added a "Pricing overrides" section to `AttributeSheet` that lists every override matching the model (including virtual-key, user, and provider-key scoped ones that don't change the displayed price), showing scope kind, match pattern, request type badges, patch field values, and a caveat explaining when context-dependent overrides apply. - Added an "overrides" badge to the "Other" column in the catalog table showing how many overrides match each model. - Extended `ModelDetails` with `overridden_pricing`, `applied_override_id`, and `pricing_override_ids` fields, and added `ModelOverriddenPricing` and `ModelPricingOverrideSummary` types to the store. - Extended `ListModelDetailsResponse` with a `pricing_overrides` index (keyed by ID) that the attributes tab resolves override IDs against, skipping any that were deleted between fetches. - Added `formatPatchValue` to render per-token/per-character patch values with the full token price formatter and all other fields as plain dollar amounts. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Configure at least one custom pricing override that matches a model in the catalog (e.g. a global override reducing input cost). 2. Open the Model Catalog tab and confirm the affected model's input/output/cache columns show the original price struck through with the new price beside it. 3. Hover the overridden price and confirm the tooltip names the override. 4. Click the edit icon for that model and confirm the "Pricing overrides" section appears in the sheet, listing the override with its scope badge, pattern, patch values, and (for virtual-key/user/provider-key scopes) the contextual caveat. 5. Confirm models with no overrides render identically to before. 6. Confirm the "Other" column shows an "overrides" badge for models with matching overrides. ```sh cd ui pnpm i pnpm build ``` ## Screenshots/Recordings _Add before/after screenshots showing the struck-through price in the table and the overrides section in the detail sheet. _  ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No new auth surfaces. Override data is already gated by RBAC on the model provider resource; the UI reads it from the same endpoint and does not expose any new write paths. ## 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
…aximhq#6059) ## Summary The active tab, search query, and provider filter in the Model Catalog are now stored in the URL query string instead of local React state. This means the view survives a page refresh and can be shared as a direct link that lands on the correct tab with filters already applied. ## Changes - The selected tab (`overview` / `attributes`) is now managed via `useQueryState` with `nuqs`, using `history: "replace"` so tab clicks don't accumulate in browser history. - The search input and provider filter in `AttributesTab` are now managed via `useQueryStates` with the same `history: "replace"` strategy, so typing doesn't flood browser history with one entry per keystroke. - When the active provider filter no longer exists in the providers list, it is cleared by setting the URL param to `null` rather than calling a local state setter. - A `parseAsSafeString` parser is used for the search and provider params to ensure safe URL deserialization. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Navigate to the Model Catalog page. 2. Switch to the **Attributes** tab, type a search term, and select a provider filter. 3. Copy the URL and open it in a new tab — it should land on the Attributes tab with the same search and provider filter pre-applied. 4. Refresh the page — the tab, search, and provider filter should all be preserved. 5. Verify that typing in the search box does not create a new browser history entry per keystroke (back button should not step through each character). 6. Verify that switching tabs does not pile up history entries. ```sh cd ui pnpm i || npm i pnpm build || npm run build ``` ## Screenshots/Recordings _Add before/after screenshots or a short clip showing URL params updating as filters change._ ## Breaking changes - [ ] Yes - [x] No ## Related issues _Link related issues here._ ## Security considerations Query params are parsed with `parseAsSafeString` and `parseAsStringLiteral` to prevent injection of arbitrary values into application state. No auth, secrets, or PII are involved. ## 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
…oad (maximhq#6096) ## Summary Eliminates the flash of the default Bifrost logo on branded enterprise deployments during client-side navigations and page reloads. Previously, the branding query was always a round trip, so every branded surface rendered the bundled Bifrost defaults until the response landed. This change persists the last known branding state to `localStorage` so the first paint can use the customer's assets immediately, without waiting for the network. ## Changes - Introduced a `localStorage` cache (`bifrost-branding`) that stores the resolved `BrandingState` after each successful branding query response. - Added `readCachedBranding` and `writeCachedBranding` helpers with guards against unparseable or malformed cache entries, and silent fallbacks when `localStorage` is unavailable (e.g. privacy mode). - A module-level variable (`cachedBranding`) is populated once per page load and kept in sync, so repeated renders don't re-parse the cache entry. - `useBranding` now falls back to the cached state (`data ?? readCachedBranding()`) while the query is in flight, rather than always falling back to the bundled defaults. - Cache writes are funneled through a single `useEffect` that fires whenever the query data updates, including after save or reset mutations that invalidate the `Branding` tag. - When branding is disabled or reset, the cache entry is removed so the defaults are correctly restored on the next load rather than showing a stale cached state. - The pre-hydration server-side shell rewrite remains in place to cover the initial document load before any of this client-side logic runs. **Trade-off:** A stale cached URL (e.g. after an admin re-uploads a logo) will 404 and show a broken image for a single frame before the in-flight response replaces it. This is considered acceptable against a guaranteed wrong-logo flash on every load. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Configure an enterprise deployment with custom branding (logo and icon uploaded). 2. Navigate to the dashboard and observe that the custom logo renders immediately on first paint without flashing the Bifrost default logo. 3. Reload the page and confirm the custom logo appears before the branding query completes. 4. Reset branding to defaults and reload — confirm the Bifrost defaults are shown and no stale cached logo appears. 5. Verify that re-uploading a logo updates the cache after the query resolves. ```sh cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` ## Screenshots/Recordings Before: Custom logo flashes the Bifrost default on every reload or client-side navigation until the branding API response lands. After: Custom logo renders immediately on first paint using the `localStorage` cache. ## Breaking changes - [x] No ## Related issues ## Security considerations Branding assets (logo/icon URLs) are stored in `localStorage`. These are content-versioned public URLs with no authentication material or PII. No sensitive data is persisted. ## 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
… to `incomplete` status with `content_filter` incomplete details (maximhq#6103) ## Summary When Bedrock returns a `content_filtered` or `guardrail_intervened` stop reason, the response was previously passed through without setting `Status` or `IncompleteDetails`, making it indistinguishable from a genuine empty completion. This PR ensures that content-filtered and guardrail-blocked turns are surfaced as `status: "incomplete"` with `incomplete_details.reason: "content_filter"` in both the non-streaming and streaming Responses API paths, as well as in the Chat-to-Responses conversion layer. ## Changes - Added a `bedrockStopReasonContentFilter` / `bedrockStopReasonGuardrailIntervened` constant pair in `utils.go` to avoid magic strings across the Bedrock provider. - In `ToBifrostResponsesResponse`, extended the stop-reason switch to handle `content_filter` and `guardrail_intervened` by setting `Status = "incomplete"` and `IncompleteDetails.Reason = "content_filter"`, matching the same pattern already used for `max_tokens` truncation. - In `FinalizeBedrockStream`, added the same case to the streaming finalization switch so the terminal SSE event is emitted as `response.incomplete` rather than `response.completed`. - In `responsesStatusFromChatFinishReason` (mux layer), added `content_filter` and `guardrail_intervened` as mapped reasons that resolve to `incomplete` + `content_filter`, so the fix applies uniformly when Chat responses are converted to Responses format. - Updated existing tests that previously treated `content_filter` as an unmapped/pass-through reason to use a genuinely unmapped reason (`some_unknown_reason` / `some_unmapped_reason`), and added new dedicated tests covering both the non-streaming and streaming content-filter paths. ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Providers/Integrations ## How to test ```sh go test ./core/providers/bedrock/... ./core/schemas/... ``` The new tests assert: - `ToBifrostResponsesResponse` with `content_filtered` or `guardrail_intervened` stop reasons produces `Status = "incomplete"` and `IncompleteDetails.Reason = "content_filter"`. - `FinalizeBedrockStream` with those stop reasons emits a `response.incomplete` terminal event with the same fields. - `ToBifrostResponsesResponse` (mux) with `content_filter` or `guardrail_intervened` finish reasons maps to `incomplete` + `content_filter`. - Genuinely unmapped stop reasons still leave `Status` unset. ## Breaking changes - [ ] Yes - [x] No ## Security considerations This change ensures content-filtered responses are never silently presented as successful empty completions, which reduces the risk of downstream agents treating a blocked turn as a valid empty output. ## 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 Introduces a `BifrostContextKeySkipProviderCheck` context flag for requests that are evaluated by governance but never routed (e.g., `/inspect`). For these requests, the provider is whatever upstream the caller was already talking to rather than an operator-selected provider, so the virtual key's provider allowlist is meaningless. Without this flag, such requests would be incorrectly blocked by the VK provider gate, and then if that were bypassed, would fail again on a model allowlist that only exists inside a provider config the VK doesn't have. Also bumps `github.com/bytedance/sonic` from v1.15.1 to v1.15.2 across all modules. ## Changes - Added `BifrostContextKeySkipProviderCheck` context key (`bifrost-skip-provider-check`) to `core/schemas/bifrost.go` and registered it as a reserved key in `core/schemas/context.go`. - `EvaluateVirtualKeyRequest` in `plugins/governance/resolver.go` now accepts a `skipProviderCheck bool` parameter. When set, the provider allowlist gate is skipped. Additionally, if the VK has no provider config for the requested provider (meaning it also has no model allowlist for it), the model gate is skipped as well. A provider the VK does configure retains its model allowlist regardless of the flag. - `EvaluateGovernanceRequest` in `plugins/governance/main.go` reads the new context key and passes it through to `EvaluateVirtualKeyRequest`. - `github.com/bytedance/sonic` bumped to v1.15.2 across all modules. - `google.golang.org/x/crypto`, `x/net`, `x/sync`, `x/sys`, and `x/text` bumped to latest patch versions in test seed modules. - Node engine constraint removed from `ui/package.json`. ## 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 Two new tests cover the behavior directly: - `TestBudgetResolver_EvaluateRequest_SkipProviderCheckAllowsUnconfiguredProvider` — verifies that a VK with no config for the requested provider allows the request when the flag is set, and does not fall through to a model block. - `TestBudgetResolver_EvaluateRequest_SkipProviderCheckKeepsModelAllowlist` — verifies that a provider the VK does configure keeps its model allowlist enforced even when the flag is set. ```sh cd plugins/governance go test ./... -run TestBudgetResolver_EvaluateRequest_SkipProviderCheck ``` ## Breaking changes - [ ] Yes - [x] No The `EvaluateVirtualKeyRequest` signature gains a new `skipProviderCheck bool` parameter. All internal call sites have been updated. External callers implementing the interface directly will need to add the parameter. ## Security considerations The flag is intended exclusively for transport-set, read-only inspection paths where the provider is not an operator choice. It bypasses the VK provider and (for unconfigured providers) model allowlists only for those requests. VK existence, active status, expiry, rate limits, and budgets are unaffected. The flag is registered as a reserved context key so it cannot be set by arbitrary plugin or caller code without going through the transport layer. ## Checklist - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
…aximhq#6075) * feat(runware): support async 3D generation via the /videos endpoint Runware exposes 3D model generation as a `3dInference` task on the same async submit-then-poll endpoint as video. The video request builder hardcoded `taskType: videoInference` and always injected 16:9 1080p width/height, so 3D tasks could not be driven through /videos. - Read an optional `taskType` from extra_params (default videoInference) so /videos can drive any Runware async task type. - Apply the width/height video defaults only for the videoInference task type; 3D uses `resolution` and rejects width/height. - Surface `outputs.files[].url` (the 3D artifact list) into the response as VideoOutput URLs, deriving content-type from the file extension (.glb -> model/gltf-binary, etc). Cost is left untracked ($0) for now; a provider-reported cost hook will follow. * feat(runware): add passthrough route for non-modeled task types Runware exposes a single task-based endpoint, so many capabilities (3D, upscaling, background removal, ...) have no first-class Bifrost surface. Implement the Passthrough method to forward raw task arrays to Runware's endpoint and return the untouched response, while Bifrost still injects the key, strips client auth, and logs the call. - Implement RunwareProvider.Passthrough + buildPassthroughURL. Runware's base URL already includes /v1, so a leading /v1 in the passthrough path is stripped to avoid duplication. - Add NewRunwarePassthroughRouter (/runware_passthrough) and register it in the integrations handler. - Add a router registration test. PassthroughStream stays unsupported (Runware polls rather than streams). Cost is left untracked ($0) for passthrough; a provider-reported cost hook reading data[].cost will follow. Verified live against a local gateway: imageInference, upscale (prunaai:p-image@upscale and runware:501@1), imageBackgroundRemoval, and 3dInference all forward correctly with key injection. * feat(runware): surface provider-reported cost across image, video/3D, and passthrough Runware returns an exact per-task `cost` (when the request sets includeCost). Surface it as the provider-reported cost so pricing uses it verbatim instead of a datasheet estimate — important for task types like 3D that have no datasheet rate. - Image generation: sum data[].cost into ImageUsage.Cost. - Video / 3D: add VideoUsage{Cost} to BifrostVideoGenerationResponse, populate it from the task result, and add a cost-engine branch that routes it through the provider-cost short-circuit. - Passthrough: add Cost to BifrostPassthroughUsage, map it in passthroughUsageToCostInput, and add ExtractRunwarePassthroughUsage reading data[].cost; wired into the Passthrough method. All paths are nil-guarded: when no cost is reported, behavior is unchanged (datasheet pricing, or $0 for raw passthrough). No other provider is affected — the new cost-engine branches only fire on the Runware-only fields. Verified live: image gen (usage.cost 0.0006) and text-to-3D via /videos (usage.cost 0.2, .glb returned) surface the reported cost end-to-end. * docs(runware): document 3D generation, passthrough route, and cost tracking Reflect the new Runware capabilities on the provider page: - 3D model generation via /videos (taskType=3dInference), incl. the image-to-3D limitation that routes users to passthrough. - The /runware_passthrough raw task-array route and async submit/poll. - Cost tracking: Runware's per-task cost (includeCost) is surfaced as the provider-reported cost and overrides datasheet pricing. * fix(runware): honor send_back_raw config in passthrough response The passthrough method ignored the provider's sendBackRawRequest / sendBackRawResponse settings. Expose the raw upstream request/response to callers via ExtraFields when those flags are enabled, matching the contract the provider's typed methods already follow (internal store_raw logging remains handled separately). Addresses CodeRabbit review. * fix(pricing): avoid mutating caller usage when attaching passthrough cost passthroughUsageToCostInput aliased the caller's su.LLMUsage into input.usage, then wrote Cost onto it in place — mutating the shared response usage on what is a pure-read cost path. Copy the usage value before assigning Cost. Adds a regression test asserting the source usage is unchanged. Addresses CodeRabbit review.
## 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
…and add customer/BU dimensions to seed manifest (maximhq#6137) ## Summary Callers with narrowed virtual key visibility (enterprise DAC-scoped principals) could bypass access controls by appending `?from_memory=true` to the list and detail virtual key endpoints. The in-memory governance snapshot carries no notion of the calling principal, so it returned every cached key regardless of entitlement. This PR gates the snapshot path behind a `virtualKeyViewScoper` check and falls through to the scoped config store for narrowed callers. A separate but related bug caused the DAC fixture manifest to under-predict visibility for shapes carrying only a customer or business unit dimension — the `computeVisibleTo` function was missing those two branches entirely. Because the negative shapes had hardcoded `VisibleTo` lists that happened to be correct, only the computed (tiggings) side disagreed with the server, surfacing as the team reader appearing to return 30 extra rows. ## Changes - **`governance.go`**: Introduced `virtualKeyViewScoper` interface and `mayServeVirtualKeysFromMemory` helper. Both `getVirtualKeys` and `getVirtualKey` now skip the snapshot and delegate to the scoped config store when the caller's view is narrowed. - **`governance_test.go`**: Added `newVKHandlerForFromMemory` factory with a `viewScoped` flag and a key present only in the snapshot, making snapshot vs. store reads distinguishable. Added tests covering the bypass fix for both the list and detail endpoints, and for the scoped caller preserving the `user_id` filter that the in-memory branch rejects. - **`seed.go`**: Added `customer_id` and `business_unit_id` branches to `computeVisibleTo`, mirroring the enterprise log scope predicate. Introduced `dacFixtureIDs` struct to replace positional string arguments, preventing silent dimension-pair swaps. Removed hardcoded `VisibleTo` lists from the three negative shapes and derived them through `computeVisibleTo` instead. - **`visibility_manifest_test.go`**: New test file pinning the manifest to the scope predicate — specifically that `only-business-unit` is visible to `team_reader_tiggings` but not `own_reader_tiggings` or `team_reader_outside`, that `legacy-unowned` stays admin-only, and that the 15-shape matrix is fully visible to the tiggings team reader. ## 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/... ./tests/cmd/seed/... ``` The new governance tests assert that a scoped caller receives a 404 (detail) or an empty store-backed response (list) rather than the snapshot contents, and that an unscoped caller still reaches the snapshot. The seed tests assert the `only-business-unit` shape carries `team_reader_tiggings` in its `VisibleTo` list and that all 15 matrix shapes are visible to the tiggings team reader. ## Breaking changes - [x] No ## Security considerations The `from_memory=true` flag on the virtual key endpoints was exploitable by any caller whose config store is DAC-scoped: appending the flag returned the full unscoped in-memory snapshot, leaking keys belonging to other customers, teams, or users. The fix is fail-closed — stores that do not implement `virtualKeyViewScoper` (OSS) are unaffected and continue to use the snapshot as before. ## Checklist - [x] I added/updated tests where appropriate - [x] I verified builds succeed (Go and UI)
964fb5e to
1834d51
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
The merge-base changed after approval.
|
|
Summary
On
/anthropic/v1/messages, thecontainerrequest param was silently dropped during the ingress/egress conversion:ToBifrostResponsesRequestnever readreq.Container, andToAnthropicResponsesRequesthad no way to restore it. A client sending"container": "container_..."to reuse a code-execution sandbox got HTTP 200 with a fresh container id and all staged files missing. Both union arms were affected (string id and object-with-skills); requests via the Claude Code raw-passthrough path were unaffected, which masked the object-form case.Changes
ToBifrostResponsesRequestcarriesreq.ContainerinExtraParams["container"], matching the existing pattern forcache_control/diagnostics/context_managementToAnthropicResponsesRequestpromotes it back onto the typedAnthropicContainerfield (typed values directly, raw string/map values via the union's decoder) and deletes the ExtraParams entry so it cannot serialize twiceTestAnthropicContainerRoundTripasserts both forms survive the round trip with id and skills intact; both subtests fail on current devType of change
Affected areas
How to test
cd core && go test ./providers/anthropic/ -run TestAnthropicContainerRoundTrip -vBoth subtests fail on dev without the fix. Full
go test ./providers/anthropic/green.Breaking changes
Related issues
Closes #5707
Security considerations
None
Checklist
docs/contributing/README.mdand followed the guidelines