Conversation
## Summary Adds v2.1.0 migration test fixtures to the migration test script, ensuring that schema changes introduced in v2.1.0 are validated when running migration tests against older releases. ## Changes - Added `append_v210_fixtures` function that probes for and applies column-level UPDATEs for new columns introduced in v2.1.0 across tables including `config_client`, `config_keys`, `config_mcp_clients`, `config_providers`, `governance_model_pricing`, `governance_virtual_keys`, `logs`, and `mcp_tool_logs`. - Added fixture rows for `enterprise_mcp_tool_groups` and `enterprise_mcp_tool_group_virtual_keys` to cover the Virtual MCP table structure preserved from the enterprise tool-group era. - Wired `append_v210_fixtures` into both the PostgreSQL and SQLite branches of `append_dynamic_mcp_clients_insert`, following the same probing pattern used by `append_v200_fixtures` so the fixture set degrades gracefully against older schema versions. ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [x] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Run the migration test workflow locally or via CI. The new fixtures will be applied automatically during the migration test run for both PostgreSQL and SQLite database types. ```sh bash .github/workflows/scripts/run-migration-tests.sh ``` Verify that the migration tests pass against both the current release and older releases without errors related to missing columns in the v2.1.0 schema. ## Screenshots/Recordings N/A ## Breaking changes - [ ] Yes - [x] No ## Related issues N/A ## Security considerations No security implications. Fixture values use test-scoped identifiers and no real secrets or PII. ## 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) - [x] I verified the CI pipeline passes locally if applicable
## Summary Fixes a decode failure for `image_generation_call` items where OpenAI emits `action` as a bare JSON string (e.g. `"generate"`) rather than an object. The previous `UnmarshalJSON` implementation immediately tried to peek at a `.type` field, which cannot be read from a JSON string, causing the entire decode to fail with `"failed to peek at type field"`. This silently dropped the `response.output_item.done` and `response.completed` stream events carrying the image, leaving the stream without a terminal event and surfacing as a bogus `"provider closed the stream"` truncation error. ## Changes - `ResponsesToolMessageActionStruct` now attempts to unmarshal the action as a bare string before falling back to the object type-peek, allowing `image_generation_call`'s `"generate"` (and similar) actions to decode correctly and round-trip through `MarshalJSON`. - `ResponsesImageGenerationCall` gains fields for the generation settings OpenAI echoes back on completed items (`background`, `output_format`, `quality`, `revised_prompt`, `size`), which were previously dropped on the native `/v1` path. - `ResponsesToolImageGeneration` gains an `action` field (`"generate"` | `"edit"` | `"auto"`) on the tool definition itself. - Tests added to lock in the bare-string action fix, guard existing object action variants against regression, verify the new `ResponsesImageGenerationCall` settings fields round-trip correctly, and cover the `action` field on the tool definition. ## Type of change - [x] Bug fix - [x] Feature ## Affected areas - [x] Core (Go) ## How to test ```sh go test ./core/schemas/... -run TestResponsesToolMessageBareStringAction go test ./core/schemas/... -run TestResponsesToolMessageObjectActionsUnchanged go test ./core/schemas/... -run TestResponsesImageGenerationCallSettings go test ./core/schemas/... -run TestResponsesToolImageGenerationAction go test ./... ``` All four new tests should pass. Existing round-trip tests for computer use, web search, and local shell actions must continue to pass without capturing the string probe. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations None. Changes are limited to JSON marshal/unmarshal logic for image generation tool call schemas. ## 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
… gossip them (#7061) * feat: route prompt cache reloads through the server so enterprise can gossip them The prompts plugin keeps an in-memory index of prompts and versions, rebuilt only by its Reload method. The HTTP handler resolved the plugin directly and called it, so the reload never left the process that served the write. In a multi-node deployment that leaves peers resolving x-bf-prompt-id and x-bf-prompt-version against a stale index until they restart. Move the reload onto ServerCallbacks as ReloadPromptCache, the same shape as ReloadProvider and ReloadVirtualKey. Enterprise overrides the method to broadcast the change to cluster peers before delegating to the local reload. Behaviour in OSS is unchanged. Also reload after session writes. Sessions are not in the plugin index, but the reload is what invalidates the UI store, so without it a session saved on one node is invisible to a browser attached to another. * chore: trim prompt cache comments to one-liners * fix: warn when the configured prompts plugin cannot reload its cache
Render access_profiles when present, including an explicit empty list, and preserve singular access_profile compatibility. Affected packages: - helm-charts/bifrost - transports Tests: - helm lint - Helm template compatibility renders - repository Helm and schema validation
## Summary Briefly explain the purpose of this PR and the problem it solves. ## Changes - What was changed and why - Any notable design decisions or trade-offs ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Describe the steps to validate this change. Include commands and expected outcomes. ```sh # Core/Transports go version go test ./... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` If adding new configs or environment variables, document them here. ## Screenshots/Recordings If UI changes, add before/after screenshots or short clips. ## Breaking changes - [ ] Yes - [ ] No If yes, describe impact and migration instructions. ## Related issues Link related issues and discussions. Example: Closes #123 ## Security considerations Note any security implications (auth, secrets, PII, sandboxing, etc.). ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary Briefly explain the purpose of this PR and the problem it solves. ## Changes - What was changed and why - Any notable design decisions or trade-offs ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Describe the steps to validate this change. Include commands and expected outcomes. ```sh # Core/Transports go version go test ./... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` If adding new configs or environment variables, document them here. ## Screenshots/Recordings If UI changes, add before/after screenshots or short clips. ## Breaking changes - [ ] Yes - [ ] No If yes, describe impact and migration instructions. ## Related issues Link related issues and discussions. Example: Closes #123 ## Security considerations Note any security implications (auth, secrets, PII, sandboxing, etc.). ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary Adds a `use_openai_endpoints` opt-in flag for Bedrock keys and aliases that routes chat completions and responses requests through Bedrock's OpenAI-compatible endpoints (`/openai/v1`) instead of Converse, for models that support them. This mirrors the existing `use_anthropic_endpoints` pattern and is intentionally opt-in because Converse carries Bedrock Guardrails, `performanceConfig`, and `requestMetadata` that the OpenAI-compatible surface silently ignores — diverting automatically could stop a guardrail from being enforced with no visible error. ## Changes - Introduces `ResolveUseOpenAIEndpoints` (alias value wins over key, matching `use_anthropic_endpoints` precedence) and replaces the narrow `runtimeServesResponses` function with a general `runtimeServesOpenAIAPI` that accepts a `BedrockAPI` discriminator and gates on the new flag. - Extends chat completions (non-streaming and streaming) to use the runtime OpenAI-compatible surface when opted in, via new `runtimeChatCompletions` and `runtimeChatCompletionsStream` methods. Previously only Responses had this path. - Adds `schemas.Bedrock` to the `responsesUsesPromptCacheBreakpoints` and `responsesUsesPromptCacheOptions` switch cases so GPT-5.6 prompt-cache handling works correctly when requests arrive on the Bedrock key rather than the Bedrock Mantle key. - Adds `use_openai_endpoints` to `Key`, `AliasConfig`, `TableKey`, all RDB read/write paths, the config redaction helper, and a new `add_use_openai_endpoints_column` migration. - Exposes the flag in the config JSON schema and in the UI provider key form as a toggle, with a description noting the Guardrails trade-off. - Existing tests updated to pass `UseOpenAIEndpoints: true` so they continue to exercise the runtime surface; new tests cover the flag's three states (unset/false/true), alias-over-key precedence, unsupported-model guard, and application-inference-profile guard. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh # Core/Transports go test ./core/providers/bedrock/... ./core/providers/openai/... ./framework/configstore/... # UI cd ui pnpm i pnpm build ``` To validate end-to-end: 1. Configure a Bedrock key with `use_openai_endpoints: true` for a model in the OpenAI family (e.g. `us.openai.gpt-5.6-terra`). 2. Send a chat completions request and a responses request — both should route through `/openai/v1/` on bedrock-runtime rather than Converse. 3. Confirm that a Claude model with the same flag set stays on Converse (AWS would 404 it on the OpenAI surface). 4. Confirm that an application inference profile stays on Converse regardless of the flag. 5. Set `use_openai_endpoints: false` at the alias level with `true` at the key level and verify the alias value wins. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations The flag does not affect credential handling. Requests on the OpenAI-compatible surface use the same SigV4 signing path as the Responses surface already did. Operators should be aware that Bedrock Guardrails configured on a key will not be enforced when this flag is enabled, since the OpenAI-compatible endpoints accept and silently ignore those fields. ## 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
Bedrock Mantle serves each model on exactly one of two URL base paths (`v1` or `openai/v1`) and returns a 400 on the other ("model isn't supported on this route"). The previous hard-coded string matching in both `core/providers/bedrock` and `core/providers/bedrockmantle` only covered known generations up to GPT-5 and Gemma 4. GPT-6 and any future closed-generation models would silently fall through to the wrong path. This PR centralises the base-path resolution into a single `ResolveBedrockMantleBasePath` function backed by the model capabilities datasheet, so new generations can be handled via a datasheet row rather than a code change.
## Changes
- Introduced `BedrockMantleBasePath` type and constants (`v1`, `openai/v1`) in `modelcapabilities.go`, with a `BedrockMantleBasePath` field on `ModelCapabilities` so the datasheet can explicitly declare the correct path per model.
- Added `ResolveBedrockMantleBasePath` in `utils.go` that applies family-name detection as a fallback (covering gpt-5, gpt-6, gemma-4, Grok → `openai/v1`; everything else → `v1`) and then defers to the datasheet value when present and valid. Unrecognised datasheet values fall back to family detection rather than silently breaking.
- Replaced the duplicated inline string-matching logic in both `core/providers/bedrock/mantle.go` and `core/providers/bedrockmantle/bedrockmantle.go` with a single call to `ResolveBedrockMantleBasePath`.
- Added `BedrockMantleBasePath` accessor on `ModelCaps` following the same pattern as `BedrockReasoningShape`.
- Extended `IsOpenAIReasoningModel`, `acceptsXHighEffort`, and `acceptsMaxEffort` in `core/providers/openai/utils.go` to cover the `gpt-6` family.
- Added tests covering family fallback, datasheet promotion, datasheet demotion, and unrecognised datasheet values, as well as new URL cases for `gpt-6-astra`.
## Type of change
- [ ] Bug fix
- [x] Feature
- [x] Refactor
- [ ] Documentation
- [ ] Chore/CI
## Affected areas
- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs
## How to test
```sh
go test ./core/schemas/... ./core/providers/bedrock/... ./core/providers/bedrockmantle/... ./core/providers/openai/...
```
The new `TestResolveBedrockMantleBasePath` suite validates:
- Family-based fallback for all known model families in both directions.
- A datasheet value promoting a model from `v1` to `openai/v1`.
- A datasheet value demoting a model from `openai/v1` to `v1`.
- An unrecognised datasheet value (e.g. `"v3"`) falling back to family detection rather than breaking.
`TestMantleOpenAIURL` covers the new `gpt-6-astra` cases for both `responses` and `chat/completions` endpoints.
## Breaking changes
- [ ] Yes
- [x] No
## Related issues
## Security considerations
None. No auth, secrets, or PII are involved.
## 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 The AWS Bedrock Converse API rejects document blocks that use a text-only `DocumentSource` unless citations are explicitly enabled. This PR fixes document handling so that all document types — including plain text formats like `text/plain`, `text/markdown`, `text/csv`, and `text/html` — always ship their content as base64-encoded bytes via `source.bytes`, never via `source.text`. ## Changes - Removed the `dataURLIsText` branching logic that previously decoded base64 data URLs and placed their content into `source.text` for text MIME types; all data URL payloads now go directly into `source.bytes`. - Removed the equivalent branch for percent-encoded data URL payloads, which previously routed text content to `source.text`. - Changed the `file_data` (non-data-URL) path so that text-format files are base64-encoded and placed in `source.bytes` instead of being assigned to `source.text`. - Updated the `BedrockDocumentSourceData.Text` field comment to note that Converse rejects it unless citations are enabled. - Updated all affected tests to assert `source.bytes` is populated (with base64-encoded content) and `source.text` is nil for text document types. - Added `TestTextDocumentUsesBytesSource` to explicitly cover `text/plain`, `text/markdown`, `text/csv`, and `text/html` formats. - Renamed `TestToolResultTextDocumentUsesSingleSourceMember` → `TestToolResultTextDocumentUsesBytesSource` to reflect the corrected behavior. ## Type of change - [x] Bug fix ## Affected areas - [x] Providers/Integrations ## How to test ```sh go test ./core/providers/bedrock/... ``` Expected: all tests pass, including the new `TestTextDocumentUsesBytesSource` and the updated `TestToolResultTextDocumentUsesBytesSource`. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations None. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary Adds E2E harness folder 77, which pins the fix for #7072: Bedrock Converse was rejecting `DocumentSource` blocks that carried only a `source.text` field ("must set one of the following keys: bytes, s3Location"). The `materializeBedrockDocument` centralisation in PR #5663 incorrectly shipped text-format documents (txt, md, csv, html) as `source.text` instead of base64-encoding them into `source.bytes`. This test folder verifies that every ingress path and document encoding variant now survives the round-trip to the model, confirmed by echoing a unique marker string rather than relying on a status-code check alone (a double-encoded document returns HTTP 200 with empty content, making status-only assertions blind to this class of bug). ## Changes - Added harness folder 77 (18 test cases) to `provider-harness.json` covering: - Raw `text/plain`, base64 data URL, percent-encoded data URL, and filename-inferred format variants via `/v1/chat/completions` - `text/html` documents (requires a full HTML document fixture because Bedrock content-sniffs the payload) - Streaming variants for both `/v1/chat/completions` and `/v1/responses` - The `/v1/responses` `input_file` call site as a separate `materializeBedrockDocument` entry point - OpenAI drop-in ingress (`/openai/v1/chat/completions`, `/openai/v1/responses`) - Anthropic drop-in ingress (`/anthropic/v1/messages`) with both `source.type: text` and `source.type: base64` - Native Bedrock Converse ingress (`/bedrock/model/{id}/converse`) with both `source.text` and `source.bytes` inputs - Tool-result document path (`function_call_output` containing an `input_file`) - A regression guard (77.07) confirming binary PDF documents continue to pass their base64 bytes through untouched - Updated `HARNESS_COVERAGE_BACKLOG.md` to mark the native Converse document block item as fully covered, referencing folder 77 and #7072. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Run the provider harness against a live environment with Bedrock credentials configured and verify folder 77 passes end-to-end. Each test asserts: 1. The response does not contain `"must set one of the following keys"` (the Bedrock rejection message). 2. The HTTP status is below 400. 3. The unique marker string (e.g. `cobalt-7701`) appears in the model's response, proving the document content survived conversion. ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes #7072 ## Security considerations None. Test fixtures contain only synthetic marker strings; no secrets or PII are involved. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary Adds support for a `use_openai_endpoints` option for Bedrock keys, allowing chat completions and responses requests to be routed through Bedrock's OpenAI-compatible endpoints (`/openai/v1`) instead of the default Converse API, for models that support them. ## Changes - Added `use_openai_endpoints` as a top-level boolean config option for Bedrock keys in the Helm chart schema and values, defaulting to `false` - Added a per-alias override of `use_openai_endpoints` in the schema, consistent with how `use_anthropic_endpoints` is handled per-alias - Documented that Bedrock Guardrails, `performanceConfig`, and `requestMetadata` only apply when using Converse, not the OpenAI-compatible endpoints ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Configure a Bedrock key with `use_openai_endpoints: true` and verify that chat completions and responses requests are routed to `/openai/v1` endpoints instead of Converse. Confirm that Guardrails, `performanceConfig`, and `requestMetadata` are not applied in this mode. Setting `use_openai_endpoints: false` (or omitting it) should preserve existing Converse behavior. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations None. This change only affects routing of requests to Bedrock endpoints and does not introduce new auth mechanisms or expose sensitive data. ## 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 Bedrock's OpenAI-compatible endpoints (`chat/completions` and `responses`, both streaming and non-streaming) apply guardrails via request headers rather than a `guardrailConfig` body field as Converse does. This PR wires `guardrailConfig` from `ExtraParams` into the correct `X-Amzn-Bedrock-Guardrail*` headers for those surfaces, and consumes the key so it is not also forwarded as a body field where it would be silently ignored. ## Changes - Added `withGuardrailHeaders` which extracts `guardrailIdentifier`, `guardrailVersion`, and optionally `trace` from a `guardrailConfig` extra param, maps them to `X-Amzn-Bedrock-GuardrailIdentifier`, `X-Amzn-Bedrock-GuardrailVersion`, and `X-Amzn-Bedrock-Trace` headers, and deletes the key from `ExtraParams` to prevent double-emission into the body. - A half-formed config (identifier present but version absent, or vice versa) is left untouched rather than sent, since both fields are required upstream. - The base header map is never mutated; `maps.Clone` is used to produce a fresh copy before writing guardrail headers. - Wired `withGuardrailHeaders` into all four runtime OpenAI handler paths: `runtimeResponses`, `runtimeResponsesStream`, `runtimeChatCompletions`, and `runtimeChatCompletionsStream`. The resulting `extraHeaders` is passed to both the SigV4 signer closure and the OpenAI handler. - Mantle is deliberately not wired because it accepts these headers but enforces nothing, meaning there is no rendering of a guardrail that actually works there. - These headers are not included in SigV4 `SignedHeaders` because AWS only requires `x-amz-*` prefixed headers to be signed, not `x-amzn-*`. Verified live that guardrails apply identically either way. - Added `TestWithGuardrailHeaders` covering: full config renders correctly and consumes the key, no config returns base unchanged, half-formed config is not sent and not consumed, and a nil base map is handled safely. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./core/providers/bedrock/... ``` To validate end-to-end, send a request to a `bedrock-runtime` OpenAI-compatible endpoint with `guardrailConfig` in `ExtraParams` and confirm the guardrail is applied. Verify that the `guardrailConfig` key is not forwarded in the request body. ## Screenshots/Recordings N/A ## Breaking changes - [ ] Yes - [x] No ## Related issues N/A ## Security considerations Guardrail identifiers and versions are forwarded as-is from caller-supplied `ExtraParams` into outbound request headers. No secrets or credentials are involved. The base header map is cloned before mutation, preventing shared state from being written through across requests. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
…compatible endpoints and report drops via `DroppedUnsupportedTools` (#7090) ## Summary Fireworks' Anthropic-compatible endpoint returns a 400 when a request includes Anthropic server tools such as `web_search_20250305`, because those tools are executed by Anthropic-operated infrastructure that does not exist on third-party hosts. Clients whose built-in web search is always on (e.g. Codex) hit this on every request. This PR fixes the failure by dropping unsupported server tools before the request leaves Bifrost, keeping the caller's function tools intact, and reporting the dropped tools on the response's `DroppedUnsupportedTools` field instead of failing the call. The same fix applies to vLLM and SGLang, which share the same Anthropic-compatible wire format and the same absence of Anthropic-hosted server tools. ## Changes - Added `ProviderFeatures` entries for `Fireworks`, `VLLM`, and `SGL` with all server-tool flags off, so the existing validators know to strip those tools for these providers. - Added `StripUnsupportedServerToolsFromRawBody`, which mirrors `ValidateChatToolsForProvider` / `ValidateResponsesToolsForProvider` for the raw-body passthrough path. Without this, a caller speaking the Anthropic dialect directly would still have its server tools forwarded even though the typed converters strip them. - Added `RecordDroppedUnsupportedTools` and `ApplyDroppedUnsupportedTools` to carry the drop list from request-building time through to the response's `ExtraFields`, matching the pattern already used by the Bedrock provider. - Wired `ValidateTools: true` and `BetaHeaderOverrides` into the Fireworks, vLLM, DeepSeek, and SGLang `Responses` / `ResponsesStream` / `ChatCompletion` / `CountTokens` call sites so tool validation and beta-header passthrough are active on those paths. - When every tool in a request is unsupported, the `tools` key is removed entirely rather than sending an empty array, which some endpoints reject. - Updated the `DroppedUnsupportedTools` doc comment to reflect that the Anthropic-family builders now populate it, not only Bedrock. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./core/providers/anthropic/... go test ./core/providers/fireworks/... go test ./core/internal/llmtests/... go test ./... ``` The new `TestResponses_AnthropicEndpointDropsServerWebSearch` and `TestChatCompletion_AnthropicEndpointDropsServerWebSearch` tests in `core/providers/fireworks/anthropic_test.go` spin up a local stub of the Fireworks Anthropic-compatible endpoint and assert that: - `web_search_20250305` is not present in the outbound request body. - The caller's function tool (`lookup`) survives. - `DroppedUnsupportedTools` on the response contains the dropped tool type. `TestAnthropicCompatibleThirdPartyProvidersRejectServerTools` in `core/internal/llmtests/provider_feature_support_test.go` asserts that Fireworks, vLLM, and SGLang have no server-tool flags set and that every known server tool type is dropped by the validator while function tools are kept. `TestStripUnsupportedServerToolsFromRawBody` in `core/providers/anthropic/rawservertools_test.go` covers the raw-body stripping path directly, including the case where all tools are unsupported and the `tools` key must be removed. ## Breaking changes - [ ] Yes - [x] No ## Related issues Fixes the Fireworks 400 `"server-side web search is not supported on this endpoint"` reported for clients with web search enabled by default. ## Security considerations No auth, secrets, or PII involved. Dropped tool names are written to a debug log line and to the response's `DroppedUnsupportedTools` field, both of which are already visible to the caller. ## Checklist - [ ] 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
…mpatible Messages endpoint (#7091) ## Summary Documents the optional Anthropic-compatible endpoint mode for the Fireworks provider, which allows Chat Completions and the Responses API to be routed through Fireworks' `/v1/messages` endpoint instead of the default OpenAI-compatible endpoints. ## Changes - Added `use_anthropic_endpoints` to the supported features list and the operations table, showing which endpoints are affected and which are not (Text Completions, Embeddings, and List Models are unaffected). - Added a new **Anthropic-Compatible Endpoints (optional)** section explaining key-level and alias-level configuration, with examples for the Web UI, API, and `config.json`. - Updated the Responses API note to clarify that Responses-only fields (`previous_response_id`, `max_tool_calls`, `store`) do not apply when `use_anthropic_endpoints` is enabled, since requests are converted to the Anthropic Messages format. - Added a warning explaining that Anthropic server and client tools (e.g., `web_search`, `computer`, `bash`) are not supported on Fireworks' Anthropic-compatible endpoint. Bifrost drops them and reports them in `dropped_unsupported_tools` rather than letting Fireworks reject the request. ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [x] Docs ## How to test Review the rendered documentation for the Fireworks provider page and verify: 1. The operations table correctly shows both default and `use_anthropic_endpoints: true` endpoint columns. 2. The new **Anthropic-Compatible Endpoints** section renders correctly across all three tabs (Web UI, API, config.json). 3. The warning block about unsupported Anthropic tools renders and is accurate. 4. The Responses API note correctly reflects the conditional behavior based on `use_anthropic_endpoints`. ## Breaking changes - [x] No ## Security considerations Authentication behavior is unchanged — Bifrost continues to send `Authorization: Bearer <key>` regardless of which endpoint mode is active. ## 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
…ndpoint routing to SGL provider docs (#7092) ## Summary Documents the optional Anthropic-compatible endpoint mode for the SGL provider, where Chat Completions and the Responses API can be routed through SGLang's `/v1/messages` endpoint instead of the default OpenAI-compatible `/v1/chat/completions` endpoint. This is controlled by the `use_anthropic_endpoints` flag, configurable at the key level or overridden per model alias. ## Changes - Updated the SGL overview to describe the optional Anthropic-compatible routing mode alongside the default OpenAI-compatible behavior. - Expanded the supported operations table to show both default and `use_anthropic_endpoints: true` endpoint columns, and added the Count Tokens operation (`/v1/messages/count_tokens`, always active regardless of the flag). - Added a dedicated "Anthropic-Compatible Endpoints (optional)" section covering authentication behavior, the `anthropic-version` header, key-level vs. alias-level configuration, and how the fallback works when neither is set. - Documented that Anthropic server/client built-in tools (`web_search`, `code_execution`, `computer`, etc.) are dropped from requests to SGLang with a `dropped_unsupported_tools` field on the response, since those tools require Anthropic-operated infrastructure. - Updated the Responses API section to clarify that the Chat Completions fallback conversion only applies when `use_anthropic_endpoints` is not enabled, and that enabling it sends requests natively to `/v1/messages`. - Added configuration examples for `config.json`, the Web UI toggle, and the API payload. ## 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 documentation for the SGL provider page and verify: - The supported operations table displays both endpoint columns correctly. - The "Anthropic-Compatible Endpoints" section renders with the Tabs component showing Web UI, API, and config.json tabs. - The Warning block about dropped built-in tools renders correctly. - The Responses API section accurately reflects the conditional fallback behavior. ## Breaking changes - [x] No ## Security considerations Authentication behavior is explicitly documented: Bifrost sends `Authorization: Bearer <key>` regardless of endpoint mode and omits the header when the key value is empty. No new secrets or auth mechanisms are introduced. ## 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
…orted_tools` response field (#7094) ## Summary Documents Bifrost's behavior of dropping Anthropic server-side tools (e.g., `web_search`, `code_execution`, `computer`) when routing requests to a self-hosted vLLM server, since those tools require Anthropic-operated infrastructure that vLLM does not implement. ## Changes - Added a "Server tool support" section to the vLLM provider docs explaining that Anthropic's built-in server and client tools are automatically dropped by Bifrost before the request reaches vLLM, with the removed tools listed in `dropped_unsupported_tools` on the response - Clarifies that user-defined function tools are never affected, and highlights the practical impact for clients that enable built-in web search by default ## 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 updated vLLM provider documentation page and verify the new section renders correctly and accurately reflects Bifrost's behavior when Anthropic server tools are present in a request targeting a vLLM endpoint. ## 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 - [x] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
…migration (#6574) ## Summary Adds support for time-of-day (peak/off-peak) pricing to the model pricing system. Some providers, such as DeepSeek, bill the same model at different rates depending on when a request is made. This introduces `off_peak_cost_multiplier` and `peak_hours` fields that allow the system to represent and persist these schedules, with the convention that all base rates are peak prices and the multiplier scales them down during off-peak windows. ## Changes - Added `PeakHoursSchedule` and `PeakHoursWindow` types to the configstore tables package, defining recurring weekly windows using IANA timezone names, weekday numbers, and `HH:MM` start/end times. The half-open interval `[Start, End)` supports midnight-wrapping windows. - Added `OffPeakCostMultiplier` and `PeakHours` fields to `TableModelPricing`, stored as a nullable float and a JSON-serialized text column respectively. - Aliased `PeakHoursSchedule` and `PeakHoursWindow` into the datasheet package so the JSON shape remains self-contained without introducing a circular import. - Wired both fields through `convertEntryToTablePricing` and `convertTablePricingToEntry` so datasheet sync round-trips correctly. - Added `off_peak_cost_multiplier` and `peak_hours` to `pricingSyncUpdateColumns` so ON CONFLICT upserts overwrite stale values. - Added the `add_time_of_day_pricing_columns` database migration to add both columns to `governance_model_pricing`, with a rollback path. - Added `TestUpsertModelPricesBatch_TimeOfDayColumns_SurviveResync` to verify that both columns survive a resync upsert and that the `peak_hours` JSON serializer round-trips correctly. The design intentionally holds base rates at peak prices. A row with a schedule but no multiplier, or one whose schedule fails to evaluate, bills at the higher rate rather than silently under-billing. ## 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/configstore/... ./framework/modelcatalog/... ``` The new test `TestUpsertModelPricesBatch_TimeOfDayColumns_SurviveResync` exercises: - Initial upsert of a row with `off_peak_cost_multiplier` and a two-window `peak_hours` schedule. - A second upsert simulating a datasheet resync with an updated multiplier value. - Verification that the updated multiplier is persisted and that the `peak_hours` JSON round-trip preserves timezone, days, and start/end times. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No auth, secrets, PII, or sandboxing implications. The new columns are additive and nullable; existing rows are unaffected. ## 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
…e resolution and tests (#6575) ## Summary Adds time-of-day (peak/off-peak) pricing support to the cost calculation engine. Providers like DeepSeek publish discounted rates during off-peak hours (e.g. 50% off outside declared peak windows). This PR evaluates a `PeakHoursSchedule` against the request's start time and scales all usage-based charges by a configured `OffPeakCostMultiplier` when the request falls outside peak windows. ## Changes - Added `offPeakMultiplier`, `isWithinPeakWindows`, `scaleUsageCost`, `parseClockMinutes`, and `peakHoursLocation` helpers to implement time-of-day pricing evaluation. - The multiplier is applied in `computeCostFromInput` after all usage-based costs are computed, so a single application site covers every modality (chat, embedding, etc.). - Flat per-request fees (`CostPerRequest`) and per-search-query fees (`SearchQueriesCost`) are explicitly excluded from the discount — only usage-based charges are scaled. - `AdditionalCost` (guardrail and MCP sidecar costs) is also excluded; those are discounted independently through their own `computeCostFromInput` calls on their own pricing rows. - `LookupScopes` gains a `BilledAt` field representing the request's start time. Using the start time keeps pricing deterministic across streaming and non-streaming responses and matches what users see in logs. A long stream crossing a window boundary bills entirely at its start-time rate. - `LookupScopesFromContext` populates `BilledAt` from `BifrostContextKeyRequestStartTime`. A zero value falls back to wall-clock time at evaluation rather than mispricing as peak. - Judge calls in `computeGuardrailJudgeCost` now inherit `BilledAt` from the parent request's scopes so they price against the same instant. - Timezone lookups are memoized in a `sync.Map` to avoid repeated filesystem hits per priced request. Failed lookups are also memoized so a bad timezone string is not retried. - The discount fails closed: any misconfiguration (missing schedule, missing multiplier, unknown timezone, malformed window, multiplier outside `(0, 1]`) bills at the peak (higher) rate rather than silently applying a discount. - Midnight-wrapping windows (e.g. 22:00–02:00) are handled by checking the previous day's window against an adjusted minute offset. - Added `cost_timeofday_test.go` covering: DeepSeek's real schedule, cache-read discounting, flat-fee exclusions, all failure-closed cases, midnight-wrapping windows, non-UTC schedules, non-text modalities, zero `BilledAt` fallback, nil scopes, `parseClockMinutes` edge cases, JSON unmarshal round-trip, and an end-to-end test loading a real datasheet file through the sync pipeline. ## 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/datasheet/... -run TestOffPeak -v go test ./framework/modelcatalog/datasheet/... -run TestParseClockMinutes -v go test ./framework/modelcatalog/datasheet/... -run TestEntryUnmarshal_TimeOfDayFields -v go test ./... ``` To exercise the discount manually, configure a model pricing row with `off_peak_cost_multiplier` and a `peak_hours` schedule, then issue requests at times inside and outside the declared windows and compare the returned cost breakdowns. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations None. The discount logic operates entirely on pricing metadata and timestamps; no user-supplied data influences the multiplier path beyond the request start time already present in context. ## 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
… to `PricingPatch` schema and patch logic (#6576) ## Summary Adds support for time-of-day (peak/off-peak) pricing on model pricing configurations. This allows usage-based charges to be discounted during off-peak hours using a configurable multiplier and a recurring weekly schedule of peak windows. ## Changes - Added `off_peak_cost_multiplier` and `peak_hours` fields to the `PricingPatch` schema in both the OpenAPI spec and the governance YAML schema. - `off_peak_cost_multiplier`: a number in `(0, 1]` applied to usage-based charges when a request falls outside declared peak windows (e.g. `0.5` for a 50% discount). Flat per-request and per-query fees are not discounted. - `peak_hours`: a recurring weekly schedule with an IANA timezone and a list of windows, each specifying weekdays (`0`–`6`), a start time (`HH:MM`, inclusive), and an end time (`HH:MM`, exclusive). End times less than or equal to start wrap past midnight. - Updated `patchPricing` in `overrides.go` to apply `OffPeakCostMultiplier` via the existing `*float64` loop and to handle `PeakHours` separately (since it is a struct pointer, not a scalar), preserving nil-means-inherit semantics. - Added `TestPatchPricing_TimeOfDayFields` covering: both fields overridden, multiplier-only override (schedule inherited from base), empty override (both fields preserved), and mutation safety of the base struct. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [x] Docs ## How to test ```sh go test ./framework/modelcatalog/datasheet/... ``` Verify that: - A model pricing override with only `off_peak_cost_multiplier` set retains the base schedule. - A model pricing override with both fields set applies both. - An empty override leaves both fields unchanged. - The base pricing struct is not mutated after patching. ## Breaking changes - [ ] Yes - [x] No ## Security considerations No auth, secrets, or PII implications. Peak/off-peak scheduling is purely a billing calculation concern applied server-side. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
…hFromForm` from sheet into `pricingFields.ts` (#6577) ## Summary Moves `FormState`, `defaultFormState`, `buildPatchFromForm`, and related types (`ScopeRoot`) out of `pricingOverrideSheet.tsx` and into `pricingFields.ts`. This allows the patch-building logic and form state definitions to be unit tested independently without importing the sheet's React component tree. ## Changes - `FormState`, `ScopeRoot`, `defaultFormState`, and `buildPatchFromForm` now live in `pricingFields.ts` alongside the other shared pricing field metadata. - `pricingOverrideSheet.tsx` re-exports these from `pricingFields.ts` to preserve the existing public API for consumers. - Required type imports (`RequestType`, `PricingOverrideMatchType`, `PricingOverridePatch`) were added to `pricingFields.ts` to support the moved 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 test || npm test pnpm build || npm run build ``` ## Screenshots/Recordings No visual changes. ## 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
…pricing overrides (#6578) ## Summary Adds support for off-peak pricing in the custom pricing override UI. This introduces an `off_peak_cost_multiplier` field and a `peak_hours` schedule type, allowing overrides to discount model costs during off-peak windows defined via the API or datasheet. It also fixes a regression where opening and saving an override in the UI would silently drop patch fields the form cannot render (such as the `peak_hours` schedule object). ## Changes - Added `off_peak_cost_multiplier` to `PRICING_FIELDS` and `PricingOverridePatch`, with validation bounds enforcing the value must be in `(0, 1]` — values outside this range are rejected by the pricing engine and would silently bill at peak rate. - Added `PeakHoursSchedule` and `PeakHoursWindow` TypeScript types to `governance.ts`. - Introduced `pricingFieldError` and `FIELD_BOUNDS` to centralize per-field validation logic, replacing duplicated inline checks in both the form's live error display and `buildPatchFromForm`. - Added `preservedPatch` to `FormState` to carry through patch fields the form cannot render (e.g. `peak_hours`). These fields are round-tripped verbatim so saving an override in the UI never drops API-authored schedule data. - Updated `toFormState` and `handleJSONChange` to populate `preservedPatch` with non-numeric or unrecognized patch keys rather than rejecting them as unknown fields. - Updated `formatPatchValue` in `attributeSheet.tsx` to handle non-numeric patch values, adding a `formatPeakHours` renderer that displays the schedule as a compact one-liner (e.g. `Mon-Fri 01:00-04:00 UTC`). - Added `pricingOverridePatch.test.ts` covering `buildPatchFromForm` behavior including the `preservedPatch` round-trip and field precedence. - Extended `pricingFields.test.ts` with a `pricingFieldError` describe block covering empty values, non-numeric input, the default non-negative rule, and the off-peak multiplier bounds. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` To validate the `preservedPatch` fix manually: 1. Create a pricing override via the API that includes a `peak_hours` schedule object alongside numeric cost fields. 2. Open that override in the custom pricing UI and save it without changes. 3. Confirm the `peak_hours` field is still present in the saved patch. To validate the off-peak multiplier: 1. Add an override with `off_peak_cost_multiplier` set to a value in `(0, 1]` — the field should accept it. 2. Attempt to set it to `0`, a negative value, or a value greater than `1` — the form should display `"Must be greater than 0 and at most 1"`. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations None. All changes are UI-side form validation and display logic with no impact on auth, secrets, or PII. ## 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
… and DeepSeek caveats (#6579) ## Summary Documents the time-of-day pricing mechanism that allows models to declare recurring weekly peak windows and an off-peak cost multiplier. This addresses providers like DeepSeek that bill at different rates depending on when a request is made, ensuring cost calculations reflect actual provider pricing rather than always applying peak rates. ## Changes - Added a "Time-of-Day Pricing" section to the model catalog architecture docs explaining how `peak_hours` and `off_peak_cost_multiplier` work, including the deterministic start-time billing rule and exclusion of flat fees from discounts. - Documented the `OffPeakCostMultiplier` and `PeakHours` fields on `PricingEntry` in the architecture reference. - Added a full `time-of-day costs` section to the custom pricing docs covering field semantics, the three governing rules (base rates are peak rates, both fields required, flat fees excluded), the `peak_hours` schedule format (IANA timezone, weekday numbers, `HH:MM` half-open intervals, midnight-wrapping windows), and UI/API editing behavior. - Added a concrete custom pricing override example mirroring DeepSeek's published Monday–Friday `01:00–04:00` and `06:00–10:00` UTC schedule at half rate. - Added a caveat accordion to the DeepSeek provider page describing the peak/off-peak billing behavior, its impact when the fields are absent, and a pointer to the custom pricing override docs. ## 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 documentation for the three updated pages: - `docs/architecture/framework/model-catalog.mdx` — confirm the new section appears under the pricing tiers section and that the `PricingEntry` struct block includes the two new fields. - `docs/providers/custom-pricing.mdx` — confirm the time-of-day table, rules, JSON example, and override example all render correctly and that the new example appears in the Examples section. - `docs/providers/supported-providers/deepseek.mdx` — confirm the new accordion appears in the Caveats section with correct severity, behavior, and impact text. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations None. This is documentation only. ## 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
…er (#7054) ## Summary Briefly explain the purpose of this PR and the problem it solves. ## Changes - What was changed and why - Any notable design decisions or trade-offs ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Describe the steps to validate this change. Include commands and expected outcomes. ```sh # Core/Transports go version go test ./... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` If adding new configs or environment variables, document them here. ## Screenshots/Recordings If UI changes, add before/after screenshots or short clips. ## Breaking changes - [ ] Yes - [ ] No If yes, describe impact and migration instructions. ## Related issues Link related issues and discussions. Example: Closes #123 ## Security considerations Note any security implications (auth, secrets, PII, sandboxing, etc.). ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
…tal` approximation (#7078) ## Summary Clarifies the accuracy guarantees of `CountRecalcTargets` and the `Total` field in `CostRecalcJobMeta`, specifically around when the count may be stale due to materialized view lag on full recalculations (`MissingCostOnly false`). ## Changes - Updated the `Total` field comment in `CostRecalcJobMeta` to explicitly note that on full recalculations it may come from a stale materialized view, and that it should never be treated as the length of the walk. - Rewrote the `CountRecalcTargets` doc comment to explain the two distinct accuracy modes: when `missingCostOnly` is set, the count comes from the raw table and is exact; when it is not set, `SearchLogs` may use `mv_logs_hourly`, which lags by its refresh interval, making the count approximate. The comment also clarifies that a stale `Total` only affects progress bar display — the worker always pages the raw table to exhaustion, so no rows are skipped or double-counted. ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test No behavioral changes. Verify the comments read correctly in context. ```sh go build ./... go test ./... ``` ## 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
## Summary
Add GA realtime transcription support for OpenAI and Azure over WebSocket and WebRTC, with normal Bifrost authentication, routing, governance, guardrails, logging, and transcription-aware pricing.
## Changes
### Problem
Normal realtime sessions identify their routing model before Bifrost connects upstream:
```text
GET /openai/v1/realtime?model=openai/gpt-realtime
└───────────────┘
routing model
```
GA transcription sessions use a different contract. A WebSocket connection carries only the intent, while the model arrives later in `session.update`:
```text
GET /openai/v1/realtime?intent=transcription
session.update
└── session.type = "transcription"
└── audio.input.transcription.model = "openai/gpt-4o-transcribe"
```
WebRTC carries the same nested model in the initial multipart `/v1/realtime/calls` request. In both cases, Bifrost must route using the transcription model while preserving the realtime connection and turn semantics.
Before this change, Bifrost could not route these sessions through aliases, virtual-key authorization, provider-key selection, governance, or provider proxy configuration. It also treated transcript text and usage like normal realtime output, which prevented correct output guardrails, logs, and transcription pricing.
### Request flow
#### WebSocket
A dedicated transcription connection authenticates before the downstream upgrade. Bifrost then buffers frames until it can discover the nested model and establish the upstream connection:
```text
client connects with intent=transcription
│
▼
authenticate before upgrade
│
▼
upgrade downstream socket
│
▼
buffer frames without rewriting them
│
▼
discover audio.input.transcription.model
│
▼
aliases → hooks → VK authorization → key selection
│
▼
dial OpenAI or Azure with intent=transcription
│
▼
replay buffered frames through the normal relay
```
Bootstrap buffering is limited to 15 seconds, 16 frames, and 1 MiB. It preserves frame type, bytes, and order. Buffered and live events pass through the same validator, so malformed JSON and unsupported binary frames behave consistently.
The long-lived session inherits the filtered combination of transport middleware and pre-request hook values. Governance identity, routing metadata, selected-key information, and raw-log settings survive the HTTP upgrade, while the completed upgrade trace ID is intentionally excluded.
Normal realtime connections with a URL model keep the eager connection path.
#### WebRTC
WebRTC receives the SDP and complete session together, so no buffering is necessary:
```text
multipart request: SDP + transcription session
│
▼
discover and route nested transcription model
│
▼
pin resolved model into session JSON
│
▼
exchange SDP with OpenAI or Azure
│
▼
use the existing media and data-channel relay
```
Bifrost resolves aliases and provider prefixes before pinning the routed model back into `session.audio.input.transcription.model`. This keeps authorization and the model sent upstream aligned.
A normal realtime session that enables optional input transcription remains a normal realtime session. Only `session.type == "transcription"` selects dedicated transcription behavior.
### Provider behavior
OpenAI and Azure distinguish model-based realtime connections from dedicated transcription intent:
```text
OpenAI normal: /v1/realtime?model=<model>
OpenAI transcription: /v1/realtime?intent=transcription
Azure normal: /openai/v1/realtime?model=<deployment>
Azure transcription: /openai/v1/realtime?intent=transcription
```
Azure WebRTC uses the same intent distinction during SDP exchange. ElevenLabs rejects transcription intent before opening its conversational-agent endpoint rather than starting a session with the wrong semantics.
### Turn handling, guardrails, and logging
A dedicated transcription turn starts when the client commits its input audio and finishes on:
```text
conversation.item.input_audio_transcription.completed
```
Normal realtime turns continue to finish on `response.done`, including normal sessions that enable optional input transcription.
Dedicated transcription uses this representation:
```text
input = audio
output = completed transcript text
```
The completed transcript is recorded before post-hooks run, allowing output guardrails to inspect it before client delivery. If a guardrail blocks the transcript, the client receives an error while logging and governance retain the completed provider response:
```text
provider completes transcription and reports usage
│
▼
output guardrail evaluates completed transcript
│
├── allowed: deliver transcript
│
└── blocked: withhold transcript and return error
│
└── retain result for logs and billing
```
Blocked turns are logged as errors with their provider result, transcript, usage, stop reason, and billable cost. Provider work remains chargeable even when Bifrost blocks delivery.
### Usage and cost
OpenAI reports normal realtime usage under `response.usage`. Dedicated transcription reports usage at the top level of its completion event in one of two forms:
```text
type = tokens → audio input, text input, and transcript output tokens
type = duration → whole or fractional audio seconds
```
Bifrost preserves fractional duration values such as `3.4` seconds through normalized usage, logging, and pricing. Both regular and streamed Responses carry the same transcription pricing inputs, including duration and the split between audio and text input tokens.
Dedicated transcription responses retain `RequestType: realtime` so hooks, raw events, and log identity stay intact. Response metadata selects the `audio_transcription` catalog mode only for cost calculation.
Token-priced transcription maps usage as follows:
```text
audio input tokens → input_cost_per_audio_token
text input tokens → input_cost_per_token
transcript tokens → output_cost_per_token
```
For example, 28 audio input tokens, 1 text input token, and 14 transcript output tokens cost `$0.0002125` with the current `gpt-4o-transcribe` pricing entry.
Duration-priced transcription, such as `whisper-1`, uses:
```text
audio seconds, including fractional seconds → input_cost_per_audio_per_second
```
For example, `3.4` reported seconds remain `3.4` through cost calculation rather than being rounded or dropped. Streamed and non-streamed Responses use the same duration and split-token mapping.
Missing pricing remains non-fatal. Normal realtime sessions continue using their existing pricing mode.
The log detail UI displays token counts for token-priced turns and audio duration with per-second pricing for duration-priced turns. Dedicated transcription logs are labeled as transcription while retaining their WebSocket or WebRTC transport label.
### GA ephemeral tokens
The supported endpoints are:
```text
POST /v1/realtime/client_secrets
POST /openai/v1/realtime/client_secrets
```
The retired beta endpoints and header are removed:
```text
POST /v1/realtime/sessions
POST /openai/v1/realtime/sessions
OpenAI-Beta: realtime=v1
```
The client-secret provider contract now represents the single GA endpoint directly, without obsolete endpoint-type state.
## Type of change
- [x] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI
## Affected areas
- [x] Core (Go)
- [x] Transports (HTTP)
- [x] Providers/Integrations
- [x] Plugins
- [x] UI (React)
- [ ] Docs
## How to test
### Focused Go tests
```sh
go test ./core \
-run 'TestRunPostLLMHooksPreservesProviderResponseWithGuardrailError' \
-count=1
go test ./core/providers/openai \
-run 'Test(ExtractRealtimeTurnUsageSupports.*TranscriptionCompletion|RealtimeWebSocketURL|NormalizeRealtimeClientSecretRequest)' \
-count=1
go test ./core/providers/azure -count=1
go test ./core/providers/elevenlabs \
-run 'TestRealtimeWebSocketURLRejectsTranscription' \
-count=1
go test ./framework/modelcatalog/datasheet \
-run 'TestCalculateCost_(RealtimeTranscriptionDurationPricing|RealtimeTranscriptionStreamDurationPricing|RealtimeTranscriptionStreamSplitTokenPricing|RealtimeTranscriptionPricingOverride|NormalRealtimeDoesNotUseTranscriptionPricing|MissingTranscriptionPricingIsNonFatal)' \
-count=1
go test ./transports/bifrost-http/handlers \
-run 'Test(ResolveRealtimeSDPTargetDedicatedTranscription|ResolveRealtimeSDPTargetRootModelPreservesNormalRealtime|PinRealtimeSDPTranscriptionModelPreservesSession|RealtimeTurnCompletionContentModelsTranscriptAsOutputOnlyForTranscriptionSessions|BuildRealtimeTurnPostResponseModelsTranscriptionAsOutput|BuildRealtimeTurnPostResponseNormalRealtimeKeepsPricingMode|BuildRealtimeTurnPostResponseMissingPricing|RealtimeTurnFinalEventUsesTranscriptionCompletionOnlyForTranscriptionSessions|DiscoverRealtimeTranscriptionModel|BufferRealtimeTranscriptionBootstrapPreservesFrames|SnapshotRealtimeMiddlewareValuesWithContext|RealtimeSessionRoutesOnlyExposeGAClientSecrets)' \
-count=1
```
### Compile and UI checks
```sh
go test ./transports/bifrost-http/handlers -run '^$'
go build ./core ./plugins/logging ./plugins/governance
cd ui
npm run typecheck
npm run lint -- app/workspace/logs/sheets/logDetailView.tsx lib/types/logs.ts
```
### Manual WebSocket verification
Connect with a normal Bifrost credential or virtual key:
```text
ws://127.0.0.1:8080/openai/v1/realtime?intent=transcription
```
Send this session update:
```json
{
"type": "session.update",
"session": {
"type": "transcription",
"audio": {
"input": {
"format": {
"type": "audio/pcm",
"rate": 24000
},
"transcription": {
"model": "openai/gpt-4o-transcribe",
"language": "en"
},
"turn_detection": null
}
}
}
}
```
Append base64-encoded PCM16 mono audio at 24 kHz and commit the input buffer. Expect `session.updated`, transcript delta events, and `conversation.item.input_audio_transcription.completed`.
Repeat with `azure/gpt-4o-transcribe` to exercise Azure routing. Use `openai/whisper-1` to verify duration-based usage and pricing.
### Verified live behavior
Live cluster checks covered:
- OpenAI WebSocket transcription success and output-guardrail blocking;
- Azure WebSocket transcription success;
- OpenAI WebRTC transcription success;
- transcript suppression on guardrail intervention;
- successful and blocked log records;
- exact request, token, and monetary-cost accounting;
- duration usage, including fractional seconds, from OpenAI `whisper-1`;
- matching transcription pricing inputs for streamed and non-streamed Responses;
- fresh virtual-key WebRTC governance accounting from a zero baseline.
Observed token-priced examples:
| Path | Result | Tokens | Cost |
| --- | --- | ---: | ---: |
| OpenAI WebSocket, guardrail blocked | error log with retained provider result | 43 | `$0.0002125` |
| OpenAI WebSocket, success | completed transcript | 44 | `$0.0002225` |
| Azure WebSocket, success | completed transcript | 43 | `$0.0002125` |
| OpenAI WebRTC, fresh virtual key | completed transcript, one request charged | 43 | `$0.0002125` |
No new configuration or environment variables are required.
## Screenshots/Recordings
Not included. The UI change is limited to displaying transcription duration and pricing metadata already present in log details.
## Breaking changes
- [x] Yes
- [ ] No
The retired beta realtime session endpoints are removed:
```text
POST /v1/realtime/sessions
POST /openai/v1/realtime/sessions
```
Clients must use the GA client-secret endpoints:
```text
POST /v1/realtime/client_secrets
POST /openai/v1/realtime/client_secrets
```
Internal realtime provider contracts also carry transcription intent and remove the obsolete client-secret endpoint-type argument.
## Related issues
No linked issue.
## Security considerations
- Authentication completes before Bifrost upgrades a transcription WebSocket, so unauthenticated clients cannot consume bootstrap buffering.
- Transcription follows normal virtual-key authorization, routing hooks, provider-key selection, aliases, governance, and proxy configuration.
- Bootstrap buffering has time, frame-count, and byte limits.
- Long-lived sessions inherit only allowlisted middleware values and do not reuse the completed upgrade trace.
- Provider credentials are never sent to downstream clients.
- Output guardrails inspect completed transcripts before Bifrost delivers them.
- This change does not add or log credentials.
## Checklist
- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
## Summary
Add Git-backed Claude Desktop and Cowork marketplace registration for skills
stored in Bifrost, while preserving the existing Claude Code marketplace flow.
## Changes
Claude Code can register the existing marketplace through its direct JSON URL:
```text
/api/skills/serve/claude-code/.claude-plugin/marketplace.json
```
Claude Desktop and Cowork use a different contract. Their marketplace form
expects a cloneable Git repository URL and rejects the direct JSON URL. It also
requires a full non-GitHub Git URL to look like a repository URL, including the
`.git` suffix.
This PR adds the following endpoint:
```text
/api/skills/serve/claude-code.git
```
The endpoint implements the two Git smart HTTP requests used during a clone:
```text
GET /api/skills/serve/claude-code.git/info/refs?service=git-upload-pack
POST /api/skills/serve/claude-code.git/git-upload-pack
```
The generated repository contains one file:
```text
.claude-plugin/
└── marketplace.json
```
Each marketplace entry continues to point to the existing per-skill Git
repository. Skill data is not copied into the marketplace repository.
```text
Claude Desktop / Cowork
│
│ git clone /api/skills/serve/claude-code.git
▼
.claude-plugin/marketplace.json
│
│ source.url
▼
/api/skills/serve/claude-code/plugins/bifrost-<skill-name>
│
│ git clone
▼
.claude-plugin/plugin.json
skills/<skill-name>/SKILL.md
```
The existing Claude marketplace JSON generation is now shared by both delivery
paths:
```text
┌─ direct JSON response for Claude Code
marketplace JSON builder ┤
└─ generated Git repository for Desktop/Cowork
```
The Skills Repository UI now offers three registration choices:
- Claude Desktop / Cowork copies the Git repository URL.
- Claude Code copies the existing CLI command.
- Codex copies the existing CLI command.
Git remains an optional runtime dependency. If an image does not contain Git,
Bifrost keeps the existing behavior of not registering Git-backed marketplace
and plugin routes. Images that need this feature must install Git separately.
## Type of change
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI
## Affected areas
- [ ] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [x] UI (React)
- [ ] Docs
## How to test
### Automated verification
From `transports/`:
```sh
go test ./bifrost-http/handlers \
-run 'Test(SkillsServingGenericFileDownloadDecodesEncodedPathParams|ClaudeMarketplaceGitRepoContainsMarketplaceAndCloneablePlugin)$'
```
The new test starts an HTTP server and performs two real Git clones. It verifies
that:
1. the Claude marketplace repository clones successfully;
2. `.claude-plugin/marketplace.json` exists;
3. the catalog advertises the expected per-skill Git URL;
4. the advertised plugin repository clones successfully;
5. `.claude-plugin/plugin.json` and `skills/<skill-name>/SKILL.md` exist; and
6. the original raw marketplace JSON endpoint still works.
From `ui/`:
```sh
npx oxlint app/workspace/skills-repo/components/skillListView.tsx
```
Expected result: zero errors. The file currently reports two pre-existing
unused-parameter warnings unrelated to this PR.
### Manual Claude Desktop / Cowork verification
Run Bifrost from an image that contains Git, then verify the endpoint directly:
```sh
git clone http://localhost:8080/api/skills/serve/claude-code.git
```
The clone should contain `.claude-plugin/marketplace.json`.
Then:
1. open the Bifrost Skills Repository;
2. select **Register as Marketplace**;
3. copy the **Claude Desktop / Cowork** URL;
4. in Claude, open **Customize → Plugins → Personal plugins**;
5. add a marketplace from a repository and paste the copied URL;
6. install and enable a Bifrost skill; and
7. start a new chat and invoke the skill with its `/` command.
This flow was tested with:
```text
http://localhost:8080/api/skills/serve/claude-code.git
```
Claude registered the marketplace, installed the plugin, loaded its skill, and
invoked it through `/` successfully.
## Screenshots/Recordings
NA
## Breaking changes
- [ ] Yes
- [x] No
The existing Claude Code and Codex marketplace URLs remain unchanged.
## Related issues
NA
## Security considerations
Marketplace and plugin serving routes remain public because Git marketplace URLs
cannot carry credentials safely. This matches the existing skills-serving
behavior. The Git implementation remains read-only and only exposes
`git-upload-pack`; it does not support pushes.
Plugins are executable instructions loaded by client applications. Users should
only install skills from Bifrost instances they trust.
## 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
## Summary
Document how to register the Bifrost Skills Repository as a personal marketplace
in Claude Desktop and Cowork, install a plugin, and invoke its skill.
## Changes
The Skills Repository guide previously documented marketplace registration for
Claude Code and Codex, but it did not explain the different flow used by Claude
Desktop and Cowork.
Claude Code accepts the marketplace JSON URL through its CLI:
```text
claude plugin marketplace add \
<bifrost-url>/api/skills/serve/claude-code/.claude-plugin/marketplace.json
```
Claude Desktop and Cowork instead expect a Git repository URL in the app:
```text
<bifrost-url>/api/skills/serve/claude-code.git
```
The guide now walks through that app flow:
```text
Bifrost Skills Repository
│
│ Copy "Claude Desktop / Cowork" URL
▼
Claude: Customize → Plugins → Add marketplace
│
│ Paste URL and sync
▼
bifrost-skills marketplace
│
│ Install and enable a plugin
▼
New chat or Cowork task → type / → select the skill
```
It includes screenshots for each point where the user moves between Bifrost and
Claude:
1. copying the Git URL from Bifrost;
2. opening Claude's **Add marketplace** action;
3. entering the repository URL;
4. choosing a Bifrost plugin;
5. confirming the plugin is installed and enabled; and
6. selecting the installed skill from the `/` command menu.
For example, a Bifrost skill named `unslop` can be installed through the
`bifrost-all-skills` plugin and then selected as:
```text
bifrost-all-skills:unslop
```
The documentation also calls out two operational boundaries:
- Git must be available in the Bifrost runtime image for marketplace serving.
- This is a personal marketplace flow. Organization-managed Claude marketplaces
use Anthropic's GitHub integration and do not accept an arbitrary Bifrost Git
URL.
## 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
1. Preview `docs/features/skills-repository.mdx` in the documentation site.
2. Open **Register Bifrost as a marketplace**.
3. Confirm the Claude Desktop and Cowork instructions appear before the existing
Claude Code and Codex commands.
4. Confirm all six screenshots render in this order:
- Bifrost marketplace URL
- Claude **Add marketplace** menu
- Claude marketplace URL dialog
- Bifrost plugin list
- installed and enabled plugin
- `/` command menu
5. Follow the documented flow against a Bifrost server whose runtime image
contains Git.
6. Confirm Claude syncs the marketplace, installs the selected plugin, and lists
its skill after typing `/` in a new chat or Cowork task.
Expected flow:
```text
<your-bifrost-url>/api/skills/serve/claude-code.git
→ Sync succeeds
→ Bifrost plugins appear
→ Plugin installs and enables
→ Skill appears in the / menu
```
No new configuration or environment variables are introduced.
## Screenshots/Recordings
The guide includes screenshots of the complete Claude Desktop marketplace setup
and skill invocation flow under `docs/media/skills-repository/`.
## Breaking changes
- [ ] Yes
- [x] No
This changes documentation only.
## Related issues
Documents the Claude Desktop and Cowork support added in #7152.
## Security considerations
The screenshots use a localhost URL and do not contain credentials, account
names, organization names, or customer hostnames.
The guide retains Claude's trust boundary: users should install plugins only
from Bifrost instances they trust. It also distinguishes personal marketplace
registration from organization-managed marketplace syncing.
## 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
### TL;DR MCP tool logs render raw UUIDs where LLM logs render names. This gives `mcp_tool_logs` the same attribution shape the `logs` table has: every governance id gets a name column beside it, written from the request context at ingestion, with nothing resolved on read. A log from the dashboard today, which is what prompted this: ```json "user_id": "eb393d61-…", "team_id": "570c2a33-…", "virtual_key_id": null, "customer_id": null, "business_unit_id": null, "project_id": null ``` ### What changed? - `MCPToolLog` gains twelve columns via `migrationAddMCPGovernanceSnapshots`. `user_name`, `team_name`, `customer_name` and `business_unit_name` stop being `gorm:"-"` transients and become storage. `team_ids`/`team_names`, `customer_ids`/`customer_names`, `business_unit_ids`/`business_unit_names` are the multi-valued sets the logs table already keeps, stored as JSON arrays and read back index-aligned. `budget_ids` and `rate_limit_ids` are the remaining governance ids `logs` records, and are id-only there too. - New `framework/logstore/governance.go` holds `MCPToolLog.ApplyGovernanceContext`, the one place that reads the context and writes the row. It lives on the struct rather than in the logging plugin because three callers across two repos need it: the logging plugin, the enterprise inspect builder, and the enterprise agent ingest handler. - `applyMCPGovernanceFieldsToEntry` collapses onto it, and the duplicate virtual key stamping either side of its two call sites goes away. - `PostMCPHook` in the governance plugin stamps `BifrostContextKeyGovernanceBudgetIDs` and `...RateLimitIDs` from the `budgets, rateLimits` pair it already computes for `UsageUpdate`. `PostLLMHook` has always done this; without it the two new columns would always be null. - `MCPToolLogEntry` in the UI types gains the array fields. No component changes: `AttributionCell` already renders name-first with an id fallback, and already handles plural arrays. Two rules are encoded in `ApplyGovernanceContext` and pinned by tests. A dimension the context does not carry leaves what is already recorded alone, so a later hook stamping a partial identity cannot blank what an earlier one knew. Changing an id clears the name beside it, because an id wearing another entity's name is worse than an id with no name. ### How to test? 1. `go test ./framework/logstore/ ./plugins/logging/ ./plugins/governance/` and `go test ./transports/bifrost-http/handlers/`. New coverage: `TestApplyGovernanceContext*` (five cases), `TestMCPToolLogGovernanceSetsRoundTrip`, `TestMCPToolLogGovernanceSetsTolerateCorruptJSON`, `TestMigrationAddMCPGovernanceSnapshots`, `TestMCPGovernanceSnapshotsMigrationIsRegistered`, `TestPostMCPHook_RecordsAccountedLimitIDs`. 2. Start against an empty logs database and confirm `mcp_tool_logs` is created with all twelve columns, then boot again to confirm the migration is a no-op. 3. Drive a gateway MCP tool call and read `GET /api/mcp-logs`. The row should carry `user_name`, `team_name` and `customer_name`, with `team_names` populated for a user in more than one team. ### Why make this change? Resolving names on every read was per-request work to answer a question the request itself had already answered. It also produced a different answer over time: a renamed team changed what an old log said about a call made before the rename, while the LLM log sitting next to it kept the original. Recording the name with the id fixes both. The row says what the entity was called when the call was made, and says it without a lookup. ### Notes The migration adds structure only. Rows written before it keep their bare ids, and nothing backfills them. The enterprise side resolves those at runtime on the single-log detail read, so a historical row is still readable where someone is actually reading it. ### Type of change - [x] Feature - [x] Database migration ### Affected areas - [x] Core (Go) - [x] UI (React)
## Summary Gemini's `Part` type was missing the `mediaResolution` field (Vertex AI v1 Part field 12), which overrides `generationConfig.mediaResolution` for a single part. Because `Part.UnmarshalJSON` decodes into a closed alias, the key was silently discarded before any conversion ran. This caused per-part image/PDF tokenization to fall back to the model default — for example, an `ULTRA_HIGH` image billed ~21k prompt tokens through `/genai` instead of ~22.1k direct. This PR adds the field end-to-end: parsing (both `mediaResolution` and `media_resolution` spellings), round-trip through the Bifrost `ResponsesMessageContentBlock`, outbound reconstruction on `convertContentBlockToGeminiPart`, and stripping on the OpenAI wire path where the field is unknown. ## Changes - **`core/providers/gemini/types.go`**: Added `MediaResolution *PartMediaResolution` to `Part` and its `MarshalJSON`/`UnmarshalJSON` alias structs. Added `PartMediaResolution` type with its own `UnmarshalJSON` that accepts both `numTokens` and `num_tokens`, with camelCase winning when both are present. Snake-case `media_resolution` is accepted as a fallback in `Part.UnmarshalJSON` using the existing `hasJSONKey` precedence pattern. - **`core/providers/gemini/responses.go`**: Added `applyGeminiPartMediaResolution` helper that stamps the per-part resolution onto the `ResponsesMessageContentBlock` produced from `inlineData` and `fileData` parts (not text/thought/function parts). Split `convertContentBlockToGeminiPart` into a builder and a wrapper that re-attaches the resolution only when the resulting part carries `InlineData` or `FileData`, preventing Gemini from rejecting the field on text parts. The value is rebuilt (not aliased) so repeated conversions across retries and fallbacks are independent. - **`core/schemas/responses.go`**: Added `MediaResolution` struct and `MediaResolution *MediaResolution` field on `ResponsesMessageContentBlock`, serialized as `media_resolution`. - **`core/schemas/utils.go`**: `deepCopyResponsesMessageContentBlock` now deep-copies `MediaResolution`, including the `NumTokens` pointer, so retry copies do not share state with the original. - **`core/providers/openai/types.go`**: `OpenAIResponsesRequestInput.MarshalJSON` now strips `MediaResolution` from content blocks in both regular messages and tool message output blocks before sending to OpenAI, matching the existing treatment of `CacheControl` and `Citations`. `hasFieldsToStripInResponsesMessage` is updated to detect `MediaResolution` in tool output blocks so the stripping path is entered. - **`tests/e2e/api/collections/provider-harness.json`**: Added harness cases 31.5–31.9 covering per-part `media_resolution` (snake_case) and `mediaResolution` (camelCase) on GenAI and Vertex, including the override-semantics case where a part-level `HIGH` must win over a request-level `generationConfig.mediaResolution: LOW`. - **`tests/integrations/python/config.json`**: Added `gemini-3.6-flash` to the GenAI model list and a Vertex Global (Gemini 3) provider entry so the new harness cases have a live target. - **`docs/openapi/`**: Documented `mediaResolution` on `GeminiPart` and added `GeminiPartMediaResolution` schema. - **Agent/skill docs**: Removed `APP_DIR`, `CI=1`, and `HARNESS_MAX_REQUESTS` from the harness command template. `APP_DIR` defaults correctly in the Makefile; `CI=1` suppresses the interactive HTML viewer; `HARNESS_MAX_REQUESTS` is now documented as optional rather than required. ## Type of change - [x] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [x] Docs ## How to test ```sh # Unit tests covering round-trip, snake/camelCase parsing, omit-stays-omitted, # non-image parts, repeated conversions, deep copy, and OpenAI stripping go test ./core/providers/gemini/... ./core/providers/openai/... ./core/schemas/... # Provider harness — per-part media resolution cases (requires genaiKey configured) make dev APP_DIR=$(pwd)/tests/integrations/python make run-provider-harness-test PROVIDER=gemini FEATURE="31" ``` Cases 31.5–31.7 run against GenAI; 31.8–31.9 run against Vertex and skip cleanly if Vertex credentials are absent. The invariant in each HIGH case is that `usageMetadata.promptTokenCount` for HIGH exceeds the value recorded by the preceding LOW case — equal counts indicate the per-part field was dropped. ## Breaking changes - [ ] Yes - [x] No ## Related issues Follows the request-level `generationConfig.mediaResolution` fix from #7138. Per-part resolution is a separate code path (typed field on `Part` rather than `ExtraParams`) and was not covered by that fix. ## Security considerations None. The change reads and forwards a provider-defined enum string and an optional integer. No credentials, secrets, or user-identifying data are involved. ## 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
## Summary Fixes two silent-success defects on native Gemini `generateContent` image requests routed through `/genai` to Vertex. Both bugs returned HTTP 200 with a well-formed image that silently ignored part of the request. **Bug 1 – Image edit detection only checked `contents[0].parts[0]`:** When a request had `responseModalities: IMAGE` with the prompt text before the `inlineData` part (text-first ordering, which matches Google's own Gemini API REST edit sample), `isImageEditRequest` returned `false`. The request was misclassified as image generation, `ToBifrostImageGenerationRequest` kept only the first text part, and the image never reached Vertex (0 IMAGE prompt tokens). The model invented a picture from the prompt alone. **Bug 2 – `imageConfig.aspectRatio` was collapsed to `1:1` for unsupported ratios:** `aspectRatio` was folded into a `WxH` size string by `convertImagenFormatToSize`, which only knows `1:1`, `3:4`, `4:3`, `9:16`, and `16:9`. Any other ratio (e.g. `3:2`, `2:3`, `21:9`) defaulted to square, and the outbound converters then derived `1:1` back from that size. The fix preserves `aspectRatio` as a typed `aspect_ratio` param that both `ToGeminiImageGenerationRequest` and `ToGeminiImageEditRequest` now prefer over the size-derived ratio. ## Changes - `isImageEditRequest` now scans all parts across all contents for `inlineData` with an image MIME type, rather than only inspecting `contents[0].parts[0]`. The `responseModalities IMAGE` check is now an early-exit guard applied before the scan. - `ToBifrostImageGenerationRequest` and `ToBifrostImageEditRequest` now copy `imageConfig.aspectRatio` into `bifrostReq.Params.AspectRatio` when it is non-empty. - `ToGeminiImageEditRequest` (and the generation equivalent) now prefer an explicit `AspectRatio` param over the ratio derived from `Size`, so arbitrary Gemini aspect ratios pass through unchanged. - `AspectRatio *string` field added to `ImageEditParameters` in `core/schemas/images.go`. - `aspect_ratio` added to `imageEditParamsKnownFields` in the HTTP handler so it is not treated as an unknown/extra param. - The provider-harness e2e collection content-detection script updated to accept `inlineData` parts as valid image content (previously only `text`, `functionCall`, and `executableCode` were accepted). - Three new e2e test cases added to the provider harness: text-first inline image reaching the model (IMAGE prompt tokens > 0), `aspectRatio 3:2` on the generation path returning a ~3:2 image, and `aspectRatio 3:2` on the edit path returning a ~3:2 image. - Unit tests added for `isImageEditRequest` at any part position and for `aspectRatio` passthrough on both image paths. ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Transports (HTTP) - [x] Providers/Integrations ## How to test ```sh go test ./core/providers/gemini/... ./transports/bifrost-http/... ``` For the aspect ratio fix, send a `generateContent` request with `generationConfig.imageConfig.aspectRatio` set to `3:2` and `imageSize` set to `2K`. Decode the returned `inlineData` image and confirm the width/height ratio is approximately 1.5 (3:2), not 1.0 (1:1). For the edit detection fix, send a `generateContent` request with `responseModalities: ["IMAGE"]` where `parts` is `[text, inlineData]` (text before image). Confirm `usageMetadata.promptTokensDetails` reports IMAGE token count > 0. The three new Postman cases in `provider-harness.json` (collection item 89) cover both fixes end-to-end against Vertex. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No auth, secrets, PII, or sandboxing changes. Inline image data in test fixtures is a minimal synthetic PNG. ## 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
## Summary Embedded client tool declarations inside `additional_tools` input items (used by Codex for local MCP functions) were not being promoted to the wire-level tool list before namespace flattening. On providers that do not understand namespace tools (e.g. Anthropic), this caused the tool declarations to be silently dropped, making the tools uncallable. This PR introduces a `hoistResponsesAdditionalTools` step that runs before namespace flattening, promoting embedded declarations into `Params.Tools` and stripping the `additional_tools` input items from the wire request, while preserving the original request for fallback attempts. ## Changes - Added `hoistResponsesAdditionalTools` in a new `core/responsestools.go` file. It promotes tools declared inside `additional_tools` input items into `Params.Tools` and removes those items from `Input`, operating copy-on-write so the shared `BifrostRequest` is never mutated. - Reordered the steps in `prepareResponsesRequest` so that `hoistResponsesAdditionalTools` runs before the namespace-support check and namespace flattening. Previously the support check ran first, meaning the hoist step was never reached for incompatible providers. - Updated the comment on `prepareResponsesRequest` to reflect the new ordering and the additional hoist step. - Added unit tests covering: promoted tool identity and argument survival across Anthropic and OpenAI wires, history alignment (forced calls and replay), copy-on-write isolation, and malformed declaration validation. - Added Anthropic `toolsearch_test.go` tests asserting the documented wire shape for `tool_search_tool_result` carries `tool_references` correctly in both streaming and non-streaming paths. - Added a Bedrock end-to-end regression test asserting that `tool_search_tool_*` server tools and `defer_loading` flags survive the invoke → Converse → neutral conversion pipeline and route to `InvokeModel`. - Added e2e Postman collection fixtures (cases 89.1 and 89.2) for non-streaming and streaming local MCP tool hoist against `anthropic/claude-sonnet-4-5`, validating that `name`, `namespace`, `call_id`, and `arguments` are correctly restored on the response. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./core/... ./core/providers/anthropic/... ./core/providers/bedrock/... ``` Key test cases to verify: - `TestPrepareResponsesAdditionalTools` — promoted tools reach the Anthropic wire with correct names and schemas; call identity is restored on response; OpenAI (namespace-capable) receives the request unchanged. - `TestPrepareResponsesAdditionalToolsHistory` — forced calls and replay items are aligned with promoted declarations. - `TestHoistResponsesAdditionalTools` — malformed declarations return errors; copy-on-write isolation is preserved. - `TestPrepareResponsesClaudeLocalMCP` — flat `mcp__local__*` tool names from Anthropic ingress survive conversion to OpenAI wire. - `TestToolSearch_WireShapeCarriesToolReferences` / `TestToolSearch_NonStreamingForwardsToolReferences` — tool search wire shape and non-streaming forwarding. - `TestToBedrockConverseRequest_InvokeToolSearchEndToEnd` — tool search and `defer_loading` survive the Bedrock invoke pipeline. ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes #7048 ## Security considerations No new auth, secrets, or PII handling introduced. The hoist step operates entirely on caller-supplied tool declarations and does not execute or forward them beyond what the caller already intended. ## 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
…th (#7162) ## Summary Briefly explain the purpose of this PR and the problem it solves. ## Changes - What was changed and why - Any notable design decisions or trade-offs ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Describe the steps to validate this change. Include commands and expected outcomes. ```sh # Core/Transports go version go test ./... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` If adding new configs or environment variables, document them here. ## Screenshots/Recordings If UI changes, add before/after screenshots or short clips. ## Breaking changes - [ ] Yes - [ ] No If yes, describe impact and migration instructions. ## Related issues Link related issues and discussions. Example: Closes #123 ## Security considerations Note any security implications (auth, secrets, PII, sandboxing, etc.). ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
#7163) ## Summary Fixes a silent no-op regression (#7155) where `tool_search_tool_*` server tools sent through the invoke ingress were silently dropped during the Converse-shaped intermediate conversion, leaving the egress predicate (`responsesUsesAnthropicInvokePath`) blind to the tool and causing the request to route to Converse instead of InvokeModel — where server-side tool search is unavailable. ## Changes - **Carry `tool_search_tool_*` as an ingress-only marker instead of dropping it.** A new `BedrockAnthropicToolSearch` struct is attached to `BedrockTool` via a `json:"-"` field so it never reaches a Converse request body but remains visible to the egress predicate. The tool is never converted to an invocable `ToolSpec`. - **Rebuild the neutral tool-search tool in `ToBifrostResponsesRequest`.** When the marker is present, the responses converter reconstructs a `ResponsesTool` of type `tool_search`, recovering the regex/bm25 variant name from `AnthropicToolSearch.Name` (or falling back to `ToolSearchVariantName` on the dated type string). - **Carry `defer_loading` across the invoke ingress.** `BedrockToolSpec` gains a `json:"-"` `DeferLoading *bool` field, propagated from the raw tool map and forwarded into the neutral `ResponsesTool`. The `cache_control` → cachePoint conversion is suppressed for deferred tools, matching Anthropic's documented constraint that a tool with `defer_loading: true` cannot also carry `cache_control`. - **Export `toolSearchVariantName` → `ToolSearchVariantName`.** The function is now exported so the invoke ingress can resolve the regex/bm25 variant using the same rule as `ResponsesTool.UnmarshalJSON`, eliminating a potential drift bug. - **Tests updated and added.** `TestConvertAnthropicTools_ToolSearchTypeNeverBecomesInvocable` is updated to assert the marker is present rather than the entry being absent. A new test, `TestConvertAnthropicTools_DeferredToolSkipsCachePoint`, pins the deferred-tool/cache-control exclusion. ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Providers/Integrations ## How to test ```sh go test ./core/providers/bedrock/... -run TestConvertAnthropicTools go test ./core/providers/bedrock/... -run TestToBedrockConverseRequest_InvokeToolSearchEndToEnd go test ./... ``` Send a request through the invoke ingress with a `tool_search_tool_regex_*` or `tool_search_tool_bm25_*` tool and confirm it routes to InvokeModel rather than Converse. Confirm a tool with `defer_loading: true` and `cache_control` does not produce a cachePoint sibling. ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes #7155 ## Security considerations None. ## 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
…ress (#7164) ## Summary When Anthropic's tool search runs on the Bedrock-native invoke path, the server-side search result must be replayed as a `server_tool_use` + `tool_search_tool_result` block pair — never as a client `tool_use`. Emitting `tool_use` causes the caller to return a `tool_result` for the `srvtoolu_...` ID, which the API explicitly forbids and rejects on the next turn. This fix ensures the correct block types are emitted and that the caller can safely echo them back unchanged. ## Changes - Added a dedicated branch in `toBedrockInvokeAnthropicResponse` that intercepts `ResponsesMessageTypeToolSearchCall` items before the generic `ResponsesToolMessage` handler, emitting a `server_tool_use` block followed by a `tool_search_tool_result` block with the nested `tool_search_tool_search_result` payload and resolved `tool_references`. - Added `ToolUseID` and `Content` fields to `BedrockInvokeMessagesContentBlock` to carry the `tool_search_tool_result` data with stable JSON key ordering. - Introduced `BedrockInvokeToolSearchResult` and `BedrockInvokeToolReference` types to represent the nested result payload in a typed, deterministically marshalled form rather than a raw map. - Added `TestToBedrockInvokeMessagesResponse_ToolSearchCall` to assert that a response containing a tool search call produces exactly `server_tool_use` + `tool_search_tool_result` + `tool_use` blocks, that the `srvtoolu_` ID never appears on a `tool_use` block, and that the downstream client tool call still drives `stop_reason`. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./core/providers/bedrock/... -run TestToBedrockInvokeMessagesResponse_ToolSearchCall -v go test ./core/providers/bedrock/... ``` The new test verifies: 1. A `tool_search_call` output item produces a `server_tool_use` block with the correct `id` and `name`. 2. The immediately following `tool_search_tool_result` block carries the correct `tool_use_id` and a `tool_search_tool_search_result` payload with the resolved `tool_references`. 3. A subsequent real function call still appears as a `tool_use` block and drives `stop_reason: tool_use`. 4. No block with a `srvtoolu_` ID has `type: tool_use`. ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes #7155 ## Security considerations None. This change only affects response block type mapping for Anthropic's server-side tool search on the Bedrock invoke path. ## 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 When Anthropic's server-side search tool (`tool_search_tool`) is used, the API returns a `tool_search_call` item with an `srvtoolu_` prefixed ID. Previously, the streaming path was emitting this as a `tool_use` content block, which would cause callers to attempt executing the server-side tool and return a `tool_result` for the `srvtoolu_` ID — something Anthropic explicitly rejects on the next turn. This PR fixes the streaming converter to emit `server_tool_use` instead of `tool_use` for `tool_search_call` items, matching the behavior already implemented on the non-streaming path. ## Changes - In `toAnthropicInvokeStreamBytes`, the `output_item.added` handler now checks whether the item type is `ResponsesMessageTypeToolSearchCall` and sets the content block type to `server_tool_use` accordingly, while ordinary `function_call` items continue to emit `tool_use`. - The `tool_search_tool_result` paired block is intentionally not re-emitted in the streaming path. The neutral stream collapses Anthropic's two blocks into a single `tool_search_call` item, and reconstructing the pair would require per-stream index state that this stateless per-chunk converter does not have. This is tracked separately; the non-streaming path already emits the full pair. - Tests cover both the `tool_search_call → server_tool_use` case and the regression case ensuring ordinary function calls still produce `tool_use`. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./core/providers/bedrock/... -run TestToBedrockInvokeMessagesStreamResponse_ToolSearchNotToolUse -v ``` Expected: both subtests pass — `tool_search_call` opens a `server_tool_use` block and an ordinary `function_call` still opens a `tool_use` block. ## Breaking changes - [ ] Yes - [x] No ## Related issues Companion to the non-streaming fix for `tool_search_call` handling. The streaming-side re-expansion of the `tool_search_tool_result` block is tracked separately. ## Security considerations None. ## 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
…ess (#7166) ## Summary When a Bedrock-native invoke request includes a tool-search conversation turn, the assistant's `server_tool_use` and `tool_search_tool_result` blocks must be echoed back unchanged on the next turn. Previously, `BedrockContentBlock.UnmarshalJSON` had no handling for these block types, so both fell through to empty structs and were silently dropped — leaving the model in a state where it had called a tool it never discovered. ## Changes - Added `server_tool_use` and `tool_search_tool_result` cases to `BedrockContentBlock.UnmarshalJSON`, populating new `AnthropicToolSearchUse` and `AnthropicToolSearchResult` carriers (tagged `json:"-"` since Converse has no wire slot for either) - Added a pre-scan in `convertSingleBedrockMessageToBifrostMessages` that pairs each `tool_search_tool_result` block to its matching `server_tool_use` by ID, so the complete `tool_search_call` item (including discovered tool references) is emitted when the use block is encountered - `tool_search_tool_result` blocks are skipped during the main content loop after being consumed by the pre-scan; `server_tool_use` blocks are converted into a neutral `ResponsesMessageTypeToolSearchCall` message that the egress converter can re-emit verbatim - Tool references are extracted from both the nested (`content.tool_references`) and flat (`tool_references`) spellings to mirror the existing `AnthropicContentBlock.DiscoveredToolReferences` behaviour - Only `tool_search_tool_` prefixed server tools are carried through; other Anthropic server tools are left unhandled to avoid silent behaviour changes ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./core/providers/bedrock/... -run TestToBedrockConverseRequest_InvokeToolSearchReplay go test ./core/providers/bedrock/... ``` The new `TestToBedrockConverseRequest_InvokeToolSearchReplay` test constructs a two-turn tool-search conversation on the invoke ingress and asserts that: 1. The replayed `server_tool_use`/`tool_search_tool_result` pair survives as a `tool_search_call` item with the correct tool references 2. The subsequent `tool_use` block calling the discovered tool is still present 3. The request continues to route through the Anthropic invoke path rather than Converse ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes #7155 ## Security considerations No auth, secrets, or PII implications. The fix is scoped to JSON unmarshalling and message conversion for a specific Anthropic server tool type. ## 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 Increases the app icon size in the MCP logs table to improve visibility and prevent icons from shrinking when space is constrained. ## Changes - Increased app icon dimensions from 14×14 to 20×20 in the MCP logs columns view - Added `shrink-0` class to prevent the icon from being compressed in flex layouts ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test Navigate to the MCP logs view in the workspace and verify that app icons appear larger and do not shrink when the column is narrow. ```sh cd ui pnpm i || npm i pnpm build || npm run build ``` ## Screenshots/Recordings Before/after screenshots of the MCP logs table showing the app icon column at both sizes would confirm the change. ## 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
…7181) ## Summary Access profiles and governance projects previously referenced Virtual MCPs by database-assigned integer IDs, making config files non-portable across environments. This PR introduces name-based resolution (`virtual_mcp_name`) for Virtual MCP assignments in both access profiles and governance projects, and replaces the `mcp_servers` / `mcp_tool_overrides` include/exclude model with a unified `mcp_configs` allowlist (`tools_to_execute`). The old keys are deprecated but still accepted and folded into the new shape at load time. ## Changes - `virtual_mcp_name` is now the canonical way to assign a Virtual MCP to an access profile or governance project. Names are resolved to stored records on startup; a name that matches nothing is refused. `virtual_mcp_id` is deprecated, still accepted, and wins when both are set. - `mcp_configs` (`{ mcp_client_id, tools_to_execute }`) replaces `mcp_servers` and `mcp_tool_overrides` on access profiles. `tools_to_execute: ["*"]` grants all tools including future ones, `[]` grants none, and a named list grants only those tools. The old exclude concept is gone — a legacy `action: exclude` entry only narrows enumerable tools. - `mcp_tool_groups`, `mcp_servers`, and `mcp_tool_overrides` are deprecated. Bifrost folds them into `virtual_mcps` / `mcp_configs` at load time with a startup-log warning instead of silently dropping them. `mcp_tool_groups` is ignored when `virtual_mcps` is present; `mcp_servers` becomes a `["*"]` allowlist except for clients already named in `mcp_configs`. - `GetVirtualMCPByName` was added to `ConfigStore` and `RDBConfigStore` to support name resolution. - `config.schema.json`, `values.schema.json`, and the OpenAPI YAML schemas were updated to declare `virtual_mcps` and `mcp_configs` and mark the retired keys as deprecated. Previously, `additionalProperties: false` on the access profile schema caused validation failures for any config using the current keys. - Documentation examples in `config-json.mdx`, `schema-reference.mdx`, `governance.mdx`, `access-profiles.mdx`, and `values.yaml` were updated to use the new spellings. - A stray merge-conflict marker was removed from `user-provisioning.mdx`. - Schema tests cover `virtual_mcp_name`/`virtual_mcp_id` acceptance and rejection cases for both access profiles and governance projects, and validate the `mcp_configs` allowlist shapes. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [x] Docs ## How to test ```sh go test ./framework/configstore/... ./transports/schema_test/... ``` - Create a Virtual MCP named `"Platform Tools"` and reference it in an access profile or governance project using `virtual_mcp_name: "Platform Tools"`. Startup should resolve it without error. - Use a name that matches no Virtual MCP and confirm startup refuses with an error. - Supply a config using the deprecated `mcp_servers` / `mcp_tool_overrides` keys and confirm a warning appears in startup logs and the grants are applied correctly. - Validate a Helm values file using `virtual_mcps` and `mcp_configs` against `values.schema.json` and confirm it passes. ## Breaking changes - [ ] Yes - [x] No Deprecated keys (`mcp_tool_groups`, `mcp_servers`, `mcp_tool_overrides`, `virtual_mcp_id`) continue to be accepted. No existing valid config is rejected. ## Related issues ## Security considerations Name resolution happens at startup against the scoped database, so a `virtual_mcp_name` that does not exist in the target environment is refused rather than silently granting no access. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary Fixes three related bugs in the OpenAI-compatible chat streaming loop where `ExtraFields.RawResponse` was attached only inside the branch that forwards content-bearing chunks. Role-only, finish-only, and usage-only frames — all documented parts of an OpenAI stream — never entered that branch, so their bytes were silently discarded. Because the framework reconstructs `raw_response` purely by concatenating `chunk.RawResponse`, these frames were absent from the captured audit trail. The usage-only frame is particularly significant: it carries the provider's authoritative token counts that Bifrost bills from, making the captured `raw_response` irreconcilable against a provider invoice. A mirror defect on the Responses-over-Chat fallback caused a single upstream chat frame spread into multiple Responses events to be stamped with the same payload, making each frame appear N times in the reconstructed output. Additionally, `delta.refusal` and `delta.annotations` were missing from the chunk-forwarding predicate, causing refusal responses and streamed URL citations to be dropped entirely. ## Changes - Introduced a `pendingRawFrames` buffer that accumulates SSE frame payloads for frames that do not produce a forwarded chunk (role-only, finish-only, usage-only). The buffer is drained onto the next forwarded content chunk, or onto the synthetic terminal chunk for trailing frames, preserving upstream order. - On the Responses-over-Chat fallback, where one upstream chat frame spreads into several Responses events, `RawResponse` is now stamped only on the first event (`i == 0`) to prevent the same frame from being concatenated once per spread event. - Added `delta.refusal` and `delta.annotations` to the chunk-forwarding predicate so that refusal-only and annotation-only deltas are forwarded to clients and accumulated correctly by `framework/streaming`. - Added unit tests covering: usage-only frame retention, finish-only frame retention, upstream ordering of all four frame shapes, refusal-only delta forwarding, annotation-only delta forwarding, and no-duplication on the Responses fallback path. - Added three e2e Postman collection cases (91.1–91.3) covering the usage-only frame surviving on the direct chat path, the finish-only frame captured exactly once, and the Responses fallback not duplicating frames. - Documented the streaming `raw_response` format in `docs/providers/supported-providers/overview.mdx`, including frame ordering, blank-line joining, the significance of the usage-only frame, and behavior on interrupted streams. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [x] Docs ## How to test ```sh go test ./core/providers/openai/... -run "TestChatStreamRawResponse|TestChatStreamForwards|TestResponsesFallbackRawResponse" ``` Expected: all five new tests pass. For e2e validation, run the Postman collection cases 91.1–91.3 against a live Bifrost instance with valid OpenAI and DeepSeek keys. Each test asserts: - 91.1: `raw_response` contains a frame with `choices: []` and `usage.prompt_tokens > 0`; normalized `total_tokens` is non-zero. - 91.2: The finish-only and usage-only frames each appear exactly once in the reconstructed `raw_response`. - 91.3: Neither the finish-only nor the usage-only frame appears more than once on the DeepSeek Responses-over-Chat fallback path. ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes #7144 ## Security considerations None. Changes are limited to how raw SSE frame bytes are buffered and attached to outgoing chunks. No auth, secrets, PII, or sandboxing implications. ## 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 Fixes #7143: when `does_not_send_done_marker` is set on a custom provider, the SSE read loop broke immediately on `finish_reason`. Because OpenAI-compatible upstreams send the usage-only frame *after* that chunk (and before `[DONE]`), the loop never read it. The synthesized terminal chunk carried a zero-valued usage object, causing logging, pricing, virtual-key usage, budgets, and cost attribution to record the request at zero tokens and zero cost. A new `wait_for_usage` flag on `custom_provider_config` tells Bifrost that the upstream does send that trailing usage-only frame, so the read loop should stay open past `finish_reason` until it arrives. Termination remains bounded: the usage chunk, two consecutive post-finish heartbeat comments, EOF, or `stream_idle_timeout_in_seconds`. ## Changes - Added `WaitForUsage bool` to `CustomProviderConfig` and a corresponding `BifrostContextKeyWaitForUsage` context key. The key is set or cleared in `requestWorker` with the same set-or-clear discipline as `DoesNotSendDoneMarker`, so it never leaks onto a fallback provider that did not declare it. - Added `WaitForStreamUsage(ctx)` helper in provider utils. - Modified the `finish_reason` break condition in both the chat-completion and text-completion OpenAI streaming loops: the loop now holds open when `wait_for_usage` is true and the usage frame has not yet been seen (`usageSeen` bool), then breaks as soon as it arrives. - `usageSeen` is set to `true` in the usage-accumulation branch of each loop, keyed on the presence of a usage object rather than on `TotalTokens > 0`, so an upstream that legitimately reports zero tokens still terminates correctly. - Added four unit tests covering: opt-in with `wait_for_usage` collects trailing usage; opt-in without `wait_for_usage` still breaks on `finish_reason` (pinned default); a silent upstream with `wait_for_usage` ends on the idle timeout with no error chunk; and the text-completion loop receives the same fix. - Added E2E Postman collection items (92.1–92.3) and two integration config entries (`openai_dnsdm`, `openai_dnsdm_wait`) to exercise the regression and its documented default against real OpenAI. - Updated `CustomProviderConfig` type, Zod schemas, and form state in the UI so `wait_for_usage` is persisted. The toggle is nested under `does_not_send_done_marker` in both the add-provider sheet and the API-structure edit fragment, and is cleared automatically when its parent flag is disabled or turned off. - Updated OpenAPI spec, Helm values schema, transport config schema, and `custom-providers.mdx` documentation with a warning that `does_not_send_done_marker` alone discards the usage frame, and guidance on pairing it with `wait_for_usage` and a low `stream_idle_timeout_in_seconds`. ## Type of change - [x] Bug fix - [x] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [x] UI (React) - [x] Docs ## How to test ```sh # Core/Transports go test ./core/providers/openai/... -run TestChat -v go test ./core/providers/openai/... -run TestText -v # UI cd ui pnpm i pnpm build ``` **Integration (requires `OPENAI_API_KEY`):** Register `openai_dnsdm` and `openai_dnsdm_wait` from `tests/integrations/python/config.json`, then run the Postman collection items 92.1–92.3 against a running Bifrost instance: - 92.1 (`openai_dnsdm_wait/gpt-4o-mini`): `total_tokens` must be > 0. - 92.2 (`openai_dnsdm/gpt-4o-mini`): `total_tokens` must be 0 (documented default, pinned). - 92.3 (`openai/gpt-4o-mini`): `total_tokens` must be > 0 (control). **New config fields:** | Field | Type | Scope | Effect | |---|---|---|---| | `custom_provider_config.wait_for_usage` | `bool` | custom providers with `does_not_send_done_marker: true` | Keeps the SSE read loop open past `finish_reason` until the trailing usage-only chunk arrives. A silent upstream then ends on `network_config.stream_idle_timeout_in_seconds`. | ## Breaking changes - [x] No `wait_for_usage` defaults to `false`. Existing `does_not_send_done_marker` behaviour is unchanged and is now explicitly pinned by a unit test and an E2E item. ## Related issues Closes #7143 ## Security considerations No new auth, secrets, or PII surface. The idle-timeout bound on `wait_for_usage` prevents a malicious or misbehaving upstream from holding a stream open indefinitely. ## Checklist - [ ] 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
…7138) * fix(gemini): keep generationConfig ExtraParams intact across retries convertParamsToGenerationConfigResponses deleted top_k, frequency_penalty, presence_penalty, stop_sequences and media_resolution from the Bifrost request's ExtraParams while mapping them into generationConfig. The conversion runs once per retry/fallback attempt on the same request, so the second attempt was sent without those settings. For Vertex/Gemini PDF workloads this silently downgraded mediaResolution to the model default on every retried 429. Read the keys without mutating the request and filter them out when the outbound ExtraParams are built, so they still never leak onto the wire as unknown snake_case fields. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(gemini): always copy ExtraParams before removing consumed keys The Responses helper returned the source map by reference when no generationConfig key was present, so the later removal of safety_settings and cached_content still mutated the Bifrost request and a retry lost them. The Chat path aliased the map the same way and dropped safety_settings, cached_content and labels on retries. Both paths now clone the map. Regression tests convert the same request three times and assert the fields and the source map on every attempt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore: retrigger checks after retargeting to dev Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* [fix]: Resolve vLLM aliases during key selection * [fix]: Expose model alias configuration for vLLM
## Summary Briefly explain the purpose of this PR and the problem it solves. ## Changes - What was changed and why - Any notable design decisions or trade-offs ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Describe the steps to validate this change. Include commands and expected outcomes. ```sh # Core/Transports go version go test ./... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` If adding new configs or environment variables, document them here. ## Screenshots/Recordings If UI changes, add before/after screenshots or short clips. ## Breaking changes - [ ] Yes - [ ] No If yes, describe impact and migration instructions. ## Related issues Link related issues and discussions. Example: Closes #123 ## Security considerations Note any security implications (auth, secrets, PII, sandboxing, etc.). ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary
OpenAI chat streams begin with a role-only delta (`{"role":"assistant","content":""}`) before any content arrives. The streaming loop previously required non-empty content (or reasoning/refusal/annotations/tool calls) before forwarding a chunk, so this initial delta was silently dropped. Strict streaming clients that rely on the role delta to assign the role of the accumulated message would receive an incomplete stream. This PR forwards the role-only delta and adds a dedicated test to assert that behavior.
## Changes
- Extended the chunk-forwarding condition in `HandleOpenAIChatCompletionStreaming` to also forward deltas that carry a non-nil `Role` field, regardless of whether content is present.
- Updated the raw-response ordering test fixture (`rawRoleOnlyFrame`) to include `"content":""` so it matches the real OpenAI wire format and is now forwarded as a semantic chunk rather than buffered.
- Removed the role-only frame from the list of "dropped frames" in the raw-response capture test comments, since it is no longer dropped.
- Added `TestChatStreamForwardsRoleOnlyDelta` to assert that the first forwarded delta carries `role:"assistant"` with empty content and that the second carries the actual text.
- Updated the thinking model in the Python integration config from `o1` to `gpt-5.5`.
- Set `asyncio_mode = "auto"` in `pyproject.toml` so LangChain's unmarked async tests run under `pytest-asyncio` without requiring strict-mode markers.
- Refactored `test_18_multi_provider_langchain_comparison` to use config-driven model names, surface per-provider errors in the assertion message, and correctly extract content strings for the uniqueness check.
## Type of change
- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI
## Affected areas
- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs
## How to test
```sh
# Run the streaming unit tests
go test ./core/providers/openai/... -run TestChatStream -v
# Confirm the new role-only forwarding test passes
go test ./core/providers/openai/... -run TestChatStreamForwardsRoleOnlyDelta -v
# Confirm raw-response ordering tests still pass
go test ./core/providers/openai/... -run TestChatStreamRawResponse -v
# Python integration tests
cd tests/integrations/python
uv run pytest tests/test_langchain.py -v
```
## Breaking changes
- [ ] Yes
- [x] No
## Related issues
Closes #7144
## Security considerations
None.
## Checklist
- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
## Summary Adds a `make test-integrations` target that runs both the Python and TypeScript SDK integration suites in parallel (one process per test file) against a single shared gateway, and improves the underlying `test-integrations.sh` script with live output streaming, a structured markdown failure report, `INTEGRATION_TEST_FILTER` support for narrowing to a single provider, and `SKIP_GATEWAY_START` support for attaching to an already-running gateway. ## Changes - **`make test-integrations`**: New Makefile target that delegates to `test-integrations.sh --parallel-files`. Detects whether a gateway is already serving `HOST:PORT` and sets `SKIP_GATEWAY_START=1` if so, reuses `tmp/bifrost-http` by default (skipping the full UI build), accepts `JOBS=N` and `INTEGRATION=<name>` overrides, and validates bash 5.1+ before spending time on secrets or builds. - **`SKIP_GATEWAY_START`**: When set, the script skips building the binary, skips starting the MCP fixture, and re-probes `/health` on the existing server rather than launching its own. `cleanup()` leaves the reused server running since it did not start it. - **`INTEGRATION_TEST_FILTER` / `--parallel-files` narrowing**: The filter is validated to `[A-Za-z0-9_-]` only (interpolated into a glob, so metacharacters would escape the test directories). In the parallel path it filters both `test_<name>.py` and `test-<name>.test.ts` lists after globbing, errors if nothing matches, and lists available integrations to help correct typos. In the sequential path it warns that the filter is ignored rather than silently running the full suite. - **Live output streaming**: `launch_test_file` now pipes each job through `tee` so lines appear as they happen. When more than one file runs concurrently, an `awk` prefix (`[label] line`) keeps concurrent output attributable. `PYTHONUNBUFFERED=1` is exported so Python does not block-buffer into the pipe. Only failed jobs are replayed at the end; passing jobs are not echoed a second time. - **Markdown failure report**: `write_failure_report` writes `test-reports/integration-failures.md` on every run (overwriting a stale red report on a green run). It extracts failed case names from pytest (`::.*FAILED`) and vitest (`× suite > case`) output after stripping ANSI escapes, copies each failed job's full log to `test-reports/integration-<slug>.log`, and includes a collapsible failure-output excerpt per file. `TEST_FINAL_STATUS[]` is introduced alongside `TEST_STATUSES[]` because the throttle's bookkeeping array is empty for jobs the final `wait` reaps. - **`mktemp` portability**: Removed `.log` suffixes from `mktemp` templates. BSD/macOS `mktemp` only substitutes trailing `X`s, so `name.XXXXXX.log` is created literally and the second run fails with "File exists". GNU `mktemp` accepts a suffix flag, which is why this only manifests outside CI. - **bash version hint**: The "bash 5.1 required" error now also prints the macOS remedy (`brew install bash`). - **Trailing whitespace**: Removed a stray trailing space in `cleanup()`. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [x] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh # Run all integration tests (starts its own gateway if none is running) make test-integrations # Run only the OpenAI integration make test-integrations INTEGRATION=openai # Attach to an already-running gateway on a non-default port make test-integrations PORT=8081 # Force a fresh binary build before running make test-integrations BUILD=1 # Limit concurrency (e.g. to avoid 429s under quota pressure) make test-integrations JOBS=4 ``` After a run, inspect `test-reports/integration-failures.md` for a structured digest and `test-reports/integration-<slug>.log` for each failed file's full output. Set `INTEGRATION_TEST_FILTER=openai` (or `INTEGRATION=openai` via the Makefile) to confirm the filter rejects invalid characters, errors on an unmatched name with a list of available integrations, and runs only the matching Python and TypeScript files. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations `INTEGRATION_TEST_FILTER` is validated to `[A-Za-z0-9_-]` before being interpolated into a glob. A path separator, glob metacharacter, or `..` sequence would be rejected, preventing the filter from reaching files outside the two `tests/` directories. `mktemp` paths use random suffixes rather than predictable names to avoid symlink-based truncation attacks in world-writable `/tmp`. ## 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
|
Important Review skippedToo many files! This PR contains 437 files, which is 137 over the limit of 300. To get a review, reduce the PR to 300 files or fewer by splitting it into smaller PRs or changing its base branch. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: ⛔ Files ignored due to path filters (30)
📒 Files selected for processing (437)
You can disable this status message by setting the Comment |
## Summary Briefly explain the purpose of this PR and the problem it solves. ## Changes - What was changed and why - Any notable design decisions or trade-offs ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Describe the steps to validate this change. Include commands and expected outcomes. ```sh # Core/Transports go version go test ./... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` If adding new configs or environment variables, document them here. ## Screenshots/Recordings If UI changes, add before/after screenshots or short clips. ## Breaking changes - [ ] Yes - [ ] No If yes, describe impact and migration instructions. ## Related issues Link related issues and discussions. Example: Closes #123 ## Security considerations Note any security implications (auth, secrets, PII, sandboxing, etc.). ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
|
|
## Summary When the Cohere-compatible HTTP route served a response from a non-Cohere provider (fallback), it returned the raw Bifrost normalized response instead of a Cohere v2-shaped payload. This caused the Cohere SDK to fail parsing the response because it expected fields like `message` and `finish_reason` rather than `choices`. ## Changes - Added `ToCohereChatResponse` in `core/providers/cohere/chat.go` to convert a normalized `BifrostChatResponse` into a `CohereChatResponse` with the correct v2 shape, including content blocks, tool calls, reasoning/thinking blocks, and token usage. - Added `ConvertBifrostFinishReasonToCohere` in `core/providers/cohere/utils.go` with a reverse mapping from canonical Bifrost finish reasons back to Cohere's `CohereFinishReason` type. Unknown reasons fall back to `COMPLETE` to satisfy Cohere's response contract. - Updated the Cohere chat route converter in `transports/bifrost-http/integrations/cohere.go` to call `ToCohereChatResponse` instead of passing the raw Bifrost response through when no native raw response is available. - Relaxed the rerank object-document round-trip assertion in the Python integration tests to allow `id` and `metadata` to be absent (as Cohere's own API omits them when the request is stringified), while still asserting they match when present. - Added unit tests covering: native Cohere raw response pass-through, cross-provider fallback conversion, and full v2 shape validation including JSON serialization (asserting `message` is present and `choices`/`extra_fields` are absent). ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [x] Transports (HTTP) - [x] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./core/providers/cohere/... go test ./transports/bifrost-http/integrations/... # Python integration tests cd tests/integrations/python pytest tests/test_cohere.py -v ``` Send a chat request through the Cohere-compatible HTTP route using a non-Cohere provider as the backend and verify the response body contains `message` and `finish_reason` at the top level rather than `choices`. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations None. This change only affects response serialization shape and does not touch authentication, secrets, or PII handling. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
✨ Features
/api/skills/serve/claude-code.gitendpoint implements the two Git smart-HTTP requests used during a clone and serves a repository containing.claude-plugin/marketplace.json; the existing Claude Code flow is unchanged (feat: adds Claude Desktop marketplace support #7152)off_peak_cost_multiplierand apeak_hoursweekly schedule, so providers like DeepSeek that bill the same model at two rates are costed correctly. Base rates are treated as peak; the multiplier scales usage-based charges outside the declared windows. Flat per-request fees, per-search-query fees and guardrail/MCPAdditionalCostare never discounted. Windows use IANA timezones, weekday numbers and half-openHH:MMintervals that may wrap past midnight, and both fields are editable from the custom pricing override sheet (feat(configstore): add time-of-day peak/off-peak pricing columns and migration #6574, feat(cost): add time-of-day peak/off-peak pricing with cached timezone resolution and tests #6575, feat(pricing): addoff_peak_cost_multiplierandpeak_hoursfields toPricingPatchschema and patch logic #6576, refactor(ui): extractFormState,defaultFormState, andbuildPatchFromFormfrom sheet intopricingFields.ts#6577, feat(ui): addoff_peak_cost_multiplierandpeak_hourssupport to pricing overrides #6578, docs: add time-of-day pricing docs for model catalog, custom pricing, and DeepSeek caveats #6579, test(e2e): add time-of-day pricing Postman collection and Newman runner #7054)intent=transcriptionon the connection and deliver the routing model later insession.update(or in the initial multipart/v1/realtime/callsrequest for WebRTC), so Bifrost now routes on the nested transcription model while preserving realtime connection and turn semantics (feat: add GA realtime transcription support #7089)allowed_models/blacklisted_modelson virtual keys andmodels/blacklisted_modelson provider keys acceptregex:<pattern>entries next to exact names. Patterns are compiled once as case-insensitive full matches, a pattern that is empty,*or does not compile is refused with 400, and list-models never surfaces a pattern as a model. Provider-key create and update now validatemodelsthe same way asblacklisted_models. This supersedes the separate*_patternsfields, which were added and then withdrawn before release (feat(schemas): add RE2 pattern lists for model allow/block rules onKeyandProviderPermit#6987, feat(models): add RE2 pattern twins for allow/block lists on provider keys and virtual key provider configs #6988, feat(ui): add regex pattern support for model allow/block lists viaModelAccessSelector#6989, docs: addallowed_models_patternsandblacklisted_models_patternsRE2 pattern fields to virtual keys, projects, and provider keys #6990, test(governance): add e2e coverage for regex entries in VK model allow/block lists #7031, revert: withdraw the model pattern fields (#6987 to #7031) #7133, feat(governance): addregex:pattern support to model allow/block lists #7134)mcp_tool_logsgains the same attribution shape thelogstable has:user_name,team_name,customer_nameandbusiness_unit_namebecome real columns instead of transients, the multi-valuedteam_ids/team_names,customer_ids/customer_namesandbusiness_unit_ids/business_unit_namessets are stored as index-aligned JSON arrays, andbudget_idsandrate_limit_idsare recorded. Names are written from the request context at ingestion, with nothing resolved on read, so the dashboard stops rendering raw UUIDs (feat(logs): record governance entity names on MCP tool logs #7154)MCPObservationcarries device, app key, server label, tool name and decision onto both the pending and final log entry, and the MCP logs view falls back toapp_keywhenappis absent so endpoint-attributed rows show the right app icon and name (Attribute endpoint MCP inspections in the standard logging pipeline #6959)virtual_mcp_name, making config files portable across environments; names resolve on startup and a name matching nothing is refused.mcp_configs({ mcp_client_id, tools_to_execute }) replaces themcp_servers/mcp_tool_overridesinclude-exclude model with a single allowlist, where["*"]grants all tools including future ones and[]grants none.virtual_mcp_idand the old keys are deprecated, still accepted, and folded into the new shape at load time (feat: reference Virtual MCPs by name on access profiles and projects #7181)error_typeMetric Label -bifrost_error_requests_totalgains anerror_typelabel with a closed, prefix-structured vocabulary (caller_*,policy_*,provider_*,bifrost_*,_OTHER) so a 429 from a governance rate limit is distinguishable from a 429 from an upstream, and a 503 Bifrost shed under queue pressure from an upstream overload. Classification resolves a declaredExtraFields.ErrorTypefirst, then Bifrost's own markers, then the status code; it deliberately ignores providererror.typestrings, which disagree across providers for the same condition (feat: add normalizederror_typelabel tobifrost_error_requests_total#7141)use_openai_endpointsflag on Bedrock keys and aliases routes chat completions and responses through Bedrock's/openai/v1surface instead of Converse, for models that support it. It is opt-in by design: Converse carries Bedrock Guardrails,performanceConfigandrequestMetadatathat the OpenAI-compatible surface silently ignores, so diverting automatically could stop a guardrail from being enforced with no visible error. The alias value wins over the key, matchinguse_anthropic_endpointsprecedence (fix: route runtime responses compat models to responses api #7071, fix: config flag to route to openai compat api in bedrock #7073)tool_search_tool_*,defer_loading) is served onbedrock/Claude models by routing those requests to InvokeModel / InvokeModelWithResponseStream, the only Bedrock API AWS allows it on; CountTokens counts such requests with the same InvokeModel body. Server-side tool search also survives the Bedrock-native invoke ingress end to end: the tool is carried as an ingress-only marker so the egress predicate can see it, results are returned as aserver_tool_useplustool_search_tool_resultpair rather than a clienttool_use(which the API rejects when echoed back), the streaming path emits the same pair, replayed blocks round-trip unchanged, and the Anthropic-native response path carries them too (tool_search in invoke flow #6900, feat(bedrock): serve Anthropic tool search on Claude via InvokeModel routing #6908, fix(anthropic): carry server-side tool search through the response path #7162, fix(bedrock): keep tool search alive across the invoke ingress (#7155) #7163, fix(bedrock): return tool search as server_tool_use on the invoke ingress #7164, fix(bedrock): stop streaming tool search as a client tool_use #7165, fix(bedrock): keep replayed tool-search blocks across the invoke ingress #7166)namespacetools are flattened in core for every provider whose wire lacks the type, with nested functions renamed to<namespace>__<function>so two namespaces sharing a function name no longer collide into an upstreamTool names must be unique400. Returnedfunction_callitems map back to the bare name plus namespace, prior-turn calls andtool_choicenames are re-aliased, and a still-duplicate name is rejected with a clear 400 before reaching the provider. Flattened names honour each wire's documented tool-name limit, overridable per model throughtool_name_max_length. The names a provider reserves for its own server tools come from the datasheet rowreserved_tool_namespaces, and Codex's literalfunctionsnamespace is unwrapped to top-level tools for every provider (fixes reserved namespace redaction for bedrock #7039, namespace tool name support across providers #7082, move tool namespace collision check to datasheet #7084, namespace flattening fix for harness tools #7161)trusted_networkslist of IP/CIDR entries the SSRF guard consults before outbound discovery calls, so a self-hosted IdP on an internal network can be reached by the generic provider's discover-endpoints and discover-claims flows. Declaring the key inconfig.jsonmakes it own the whole list, an explicit empty array clears dashboard-added entries, and omitting it leaves the stored allowlist untouched. Hostnames are refused, since DNS would then decide which requests bypass SSRF protection (feat: add trusted_networks to Helm and config schema #7081)ReloadPromptCachemoves ontoServerCallbacksso enterprise can gossip it across nodes. The prompts plugin's in-memory index was previously rebuilt only in the process that served the write, so on a multi-node deployment a prompt published on node A left node B resolvingx-bf-prompt-id/x-bf-prompt-versionagainst a stale index until restart: an unknown version errored andlatestserved the old content. OSS behaviour is unchanged (feat: route prompt cache reloads through the server so enterprise can gossip them #7061)delta.partial_jsononinput_json_deltaevents and collecting string-valued paths inside atool_useblock'sinputwithout touching tool names, IDs or definitions. A separate identity-based transformer path lets provider-managed rewrites (Model Armor, Bedrock) land in the correct native JSON field even when the same text appears in several fields, verifyingOriginalbefore patching and the written value after (oss changes for support for tool calls args redaction in guardrails #6977, oss: passthru redaction for transformed output guardrails like model armor, bedrock #7049)SecretVar.RedactedIfSecret()returns a plain clone for a literal value and still masks anything sourced from an env var or vault reference (feat(redaction): surface literal regions and service URLs in plaintext viaRedactedIfSecret#7085)wait_for_usagefor Custom Providers - Await_for_usageflag oncustom_provider_configtells Bifrost the upstream sends a trailing usage-only frame, so the read loop holds open pastfinish_reasonuntil it arrives instead of synthesizing a zero-usage terminal chunk. Termination stays bounded by the usage chunk, two consecutive post-finish heartbeat comments, EOF, orstream_idle_timeout_in_seconds(wait_for_usage flag for custom providers #7187)buildMCPHeaders(), so OAuth emits no headers, identity provider emits aBearerplaceholder, and virtual key keepsx-bf-vk(feat(mcp-usage-guide): add OAuth and IdP token auth methods alongside virtual key #7111)🐞 Fixed
ConvertToBifrostContextstarts a socket watcher that peeks the client connection every 500 ms withMSG_PEEKand cancels the context on FIN or RST, so upstream retries stop as soon as nobody is listening; previously fasthttp offered no per-requestDoneand a disconnect was only noticed when an SSE write failed. No-op on non-unix platforms and on in-memory test connections ([Bug]: upstream retries continue after the client has disconnected, and go pastmax_retries— one abandoned request produced 10 upstream attempts over ~20 minutes #7035, cancel in-flight requests when the client closes its socket before the first byte #7106)default_request_timeout_in_secondsnow bounds the wait for response headers on streaming requests, and cancelling a request closes the upstream socket. Every fasthttp client runs through a Bifrost-ownedRoundTripperthat applies the client's read/write timeouts and the request context to the request write and the header wait, then lifts the socket deadline once headers are parsed sostream_idle_timeout_in_secondsremains the only bound on the body. An upstream that accepts the connection and never answers now fails with 504RequestTimedOutand its fallbacks are used, instead of pinning the provider worker. Unary large-response downloads bound every body read the same way, gzip-encoded bodies are classified for large-response mode by decompressed size, and a mid-body connection drop is again reported as the retryable 502 completion-marker error instead of a generic unexpected EOF ([Bug]:default_request_timeout_in_secondsandstream_idle_timeout_in_secondsdo not fire while waiting for response headers — a silent upstream blocks the request until the upstream closes, and the fallback is never used #7034, adds custom tripper to fasthttp streaming clients #7104)max_retries.contextTransport.RoundTripreports a pre-header failure on a freshly dialed socket withretry=false, soStaleConnectionRetryIfErronly walks past pooled keep-alive sockets the upstream closed while idle; an upstream that closes a fresh connection without answering now costs exactly one attempt instead of up to four. Retry backoff also ends as soon as the request context is cancelled, freeing the worker immediately instead of after up toretry_backoff_max([Bug]: upstream retries continue after the client has disconnected, and go pastmax_retries— one abandoned request produced 10 upstream attempts over ~20 minutes #7035, scope fasthttp stale-connection retries to pooled sockets and end retry backoff on cancel #7105)selecthad two simultaneously ready cases, a send into a cap-1 channel andctx.Done(), and Go picks uniformly among ready cases, so terminal post-hooks were skipped roughly 50% of the time. The worker now checksreq.Context.Err()before the select and callsbillAbandonedTerminaldeterministically, keeping the 5-second timer guard for a caller that leaves between the check and the send ([Bug]: abandoned-request billing is a ~50% coin flip when a client disconnects mid-request #6972, fixes caller cancellations detections #7116)[DONE]- An OpenAI-compatible upstream that omits[DONE]and then goes silent afterfinish_reasonno longer fails the stream whenstream_idle_timeout_in_secondsfires. The chat and text completion read loops treat an idle timeout after a terminal signal as a parked upstream, abandon the connection rather than drain it, and synthesize the final chunk with the bufferedfinish_reason; a stall beforefinish_reasonstill surfaces as the idle-timeout error ([Bug]: Custom-provider streaming never terminates when the upstream omits [DONE] (heartbeats mask stream_idle_timeout_in_seconds) #7108, fixes missing stream termination for custom providers #7115)raw_response- Role-only, finish-only and usage-only frames never entered the chunk-forwarding branch, so their bytes were discarded from the reconstructedraw_response, leaving the captured audit trail irreconcilable against a provider invoice since the usage frame carries the token counts Bifrost bills from. ApendingRawFramesbuffer drains onto the next forwarded chunk or the synthetic terminal chunk, the Responses-over-Chat fallback no longer stamps one upstream frame onto every derived event, anddelta.refusalanddelta.annotationsare forwarded instead of dropped entirely ([Bug]: Chat Completions streaming raw_response omits usage-only and finish-only SSE frames #7144, fixes: dropped sse chunks in raw response #7184)finish_reason;ProviderSendsDoneMarkernow treatsbedrock_mantleand the legacy Mantle route under thebedrockkey as sending[DONE], so streamed usage and cost are recorded ([Bug]: Bedrock Mantle chat streaming drops trailing usage after finish_reason #7065, fixes bedrock mantle streaming missing usage #7076)prepareFallbackRequesthad no arm for those three types, so the shallow copy kept the primary's sub-request pointer and the attempt was routed back to the primary while routing info, headers, the log row and thefallback_indexmetric label all reported it as a fallback. The helper now verifies the prepared request targets the fallback and skips it with a warning otherwise, so a future request type added without an arm fails loudly ([Bug]: fallbacks silently re-target the primary provider for image edit / variation requests #6966, image & video model fallbacks fixes #7118)clearCtxForFallbackmissedBifrostContextKeyProviderResponseHeaders. Providers set that key before the status check so error paths can forward it, and when a fallback failed pre-flight (key selection failed, a plugin short-circuited, the queue was retiring) nothing overwrote it, so the client received a response attributed to provider B carrying provider A'sRetry-Afterand rate-limit headers ([Bug]: provider response headers leak across fallback boundaries (clearCtxForFallback misses ProviderResponseHeaders) #6973, fix(core): clear provider response headers on fallback boundaries #7021) (thanks @Huang-404-Q!)extra_fields.provider_response_headers. The path now also consultsschemas.IsSensitiveHeader, which recognizes credential names by substring and suffix and already knew aboutcf-access-*andx-amzn-oidc-*; a fixed list cannot enumerate the space whennetwork_config.extra_headersexists to carry custom auth headers and some upstreams echo request headers back ([Bug]: Provider response-header filter ignores IsSensitiveHeader, forwarding credential-named headers to inference callers #7120, fix: filter credential-bearing provider response headers by classifier #7121) (thanks @Atharva-Kanherkar!)BifrostErrorwhose nestedErrorfield is nil crashed the request worker. Fallback processing now nil-checks the error and guards access toError.Type, using the nil-safeGetErrorString()helper, and continues to the next fallback when allowed ([Bug]: shouldContinueWithFallbacks nil-derefs BifrostError.Error, crashing the process on a plugin-returned error #6967, fix: handle incomplete fallback errors #7110) (thanks @Constantine3!)document, and Converse rejects duplicate document names, so any request with two or more untitled documents failed unconditionally with aValidationException. A per-request document namer now disambiguates with numeric suffixes (document,document-2, and so on) and suffixes titled documents only on an actual collision, on both the Converse and Responses replay paths ([Bug]: Bedrock Converse assigns duplicate default name "document" to untitled document blocks → ValidationException #7003, fix(bedrock): assign unique names to untitled document blocks #7027) (thanks @Huang-404-Q!)DocumentSourceunless citations are explicitly enabled, so plain text formats (text/plain,text/markdown,text/csv,text/html) failed withmust set one of the following keys: bytes, s3Location. All document content, including data URLs, percent-encoded payloads andfile_data, now ships base64-encoded throughsource.bytes([Bug]: Bedrock Converse drops text-format document bytes → DocumentSource "must set one of the following keys: bytes, s3Location" (v1→v2 regression) #7072, fix: bedrock document source #7079)toolResultin Converse, even though they accept images in tool output via Responses. AhoistToolResultImagespass moves images out oftoolResultblocks and re-inserts them after the last tool result in the same message, leaving a placeholder text block so the now image-free result is not rejected for being empty. A datasheet rowsupports_converse_tool_result_imagesoverrides the family-level default (fix: bedrock image block in tool result #7150)POST /anthropic/v1/messagesat a Bedrock Mantle model was rejected with hundreds of validation errors: the Bedrock-grouped ingress converter tagged user and system input text asoutput_text(onlyinput_textis valid on input messages) and omitted the requiredstatuson replayed assistant messages. Bedrock requests with no explicitmax_tokensnow also populate it from the model's known capacity instead of truncating silently ([Bug]: openai.gpt-oss-120b via Bedrock Responses API mistags replayed history as output_text instead of input_text, breaks multi-turn Claude Code sessions #7074, fixes gpt-oss mistag #7075)/openai/v1/responsesbecause Microsoft routes those models through chat completions internally. The Azure provider now checks the model's datasheetsupported_endpointsand transparently serves bothResponsesandResponsesStreamthrough/openai/v1/chat/completionswhen/v1/responsesis absent. Separately, a turn truncated by the output-token cap on any OpenAI-shaped Responses provider now reportsstop_reason: max_tokenson the Anthropic egress instead of hiding the truncation asend_turn([Bug]: Azure Fireworks/Foundry models capped at 4096 output tokens on Responses + Anthropic ingress (chat completions is not); truncation reported as end_turn #6782, fixes azure-firework model output token capping fix #7142)inlineData) was silently dropped on/v1/chat/completions, both unary and streaming ([Bug]: Gemini image-generation output (inlineData) silently dropped on /v1/chat/completions, both unary and streaming #7032, [fix]: Gemini provider - preserve inline image and audio data in chat completions #7033) (thanks @Atharva-Kanherkar!)isImageEditRequestonly checkedcontents[0].parts[0], so a request with the prompt text before theinlineDatapart, which is the ordering in Google's own REST edit sample, was misclassified as image generation: the image never reached Vertex and the model invented a picture from the prompt alone, returning HTTP 200. Detection now scans all parts across all contents.imageConfig.aspectRatiois also preserved as a typed parameter instead of being folded into aWxHsize string that collapsed any unsupported ratio to1:1(fix: image edit check in genai #7173)Partwas missingmediaResolution, which overridesgenerationConfig.mediaResolutionfor a single part, and becausePart.UnmarshalJSONdecodes into a closed alias the key was discarded before any conversion ran. Per-part image and PDF tokenization fell back to the model default, so anULTRA_HIGHimage billed about 21k prompt tokens through/genaiinstead of about 22.1k direct. The field now round-trips both spellings end to end and is stripped on the OpenAI wire path where it is unknown (fix/preserve-per-part-media-resolution #7156)generationConfigLost Across Retries -convertParamsToGenerationConfigResponsesdeletedtop_k,frequency_penalty,presence_penalty,stop_sequencesandmedia_resolutionfromExtraParamswhile mapping them intogenerationConfig, and that conversion runs once per attempt on the same request, so every retry or fallback after the first was sent without them. On Vertex the only visible signal waspromptTokenCounthalving on retried requests, making the downgrade silent (fix(gemini): keep generationConfig ExtraParams intact across retries #7138) (thanks @VictorRequenaMaisa!)$defsorder across tool syncs, so prompt caching is not invalidated by reordering alone ([Bug]: MCP tool schema property order changes between tool syncs, breaking prompt caching #7169, fix: sort MCP tool schema properties and $defs on conversion #7170) (thanks @dougcalobrisi!)model_nameagainst the unresolved alias and rejected valid keys. The alias is now resolved per key before that comparison, so the same public alias can map to different physical model IDs across vLLM instances, while allow and block checks keep matching the original alias. Allowed Models, Blocked Models and Deployments/Aliases are now exposed on the vLLM key form ([fix]: Resolve vLLM aliases during key selection #6956) (thanks @Constantine3!)/v1/chat/completions, which its upstream serves, instead of/v1/responses, which it does not ([Bug]: opencode-zen Anthropic endpoint fails — zen upstream doesn't support /v1/responses #6778, [fix]: route opencode-zen Responses calls through /v1/chat/completions #6819) (thanks @miguelchico!)max_completion_tokensIgnored - DeepSeek's chat-completions endpoint only recognizes the legacymax_tokensfield and silently ignoresmax_completion_tokens, so the limit had no effect. The field is now remapped on the wire, matching the behaviour already in place for Opencode and Ollama (fix: max tokens for deepseek provider #7131)web_search_20250305, because those run on Anthropic-operated infrastructure that does not exist on third-party hosts; clients whose built-in web search is always on hit this on every request. Unsupported server tools are now dropped before the request leaves Bifrost, the caller's function tools are kept, and the drops are reported onDroppedUnsupportedToolsinstead of failing the call. The same applies to vLLM and SGLang (fix: strip Anthropic server tools on Fireworks/vLLM/SGLang Anthropic-compatible endpoints and report drops viaDroppedUnsupportedTools#7090)guardrailConfigbody field Converse uses, so a configured guardrail was silently ignored there.guardrailIdentifier,guardrailVersionandtraceare now mapped to theX-Amzn-Bedrock-Guardrail*headers on both chat completions and responses, streaming and non-streaming, and the key is consumed so it is not also emitted into the body. A half-formed config with only one of identifier or version is left untouched rather than sent (fix: guardrail identifier in runtime headers #7095)item: null- Responses stream events that carry no item payload no longer serialize"item": null, which strict OpenAI Responses clients reject as an invalid frame, breaking streamed/v1/responsesusage (fixes responses sse item null regression #6395) (thanks @ReStranger!)actionString Decode -image_generation_callitems where OpenAI emitsactionas a bare JSON string failed to decode, becauseUnmarshalJSONimmediately peeked at a.typefield that cannot be read from a string. That silently dropped theresponse.output_item.doneandresponse.completedevents carrying the image, leaving the stream without a terminal event and surfacing as a bogus "provider closed the stream" truncation error. The completed item also keeps the generation settings OpenAI echoes back (fix: responses event action string #7060)role: "system"messages are inlined in place as<system-reminder>user turns on every converter with a top-level system field: Bedrock Converse (Responses and Chat Completions), Gemini Chat Completions, and the Anthropic wire shape used by DeepSeek, Fireworks and SGL. Previously only Claude on Bedrock and Anthropic inlined; everything else hoisted each reminder into the top-level system block. Claude Code appends a trailing<total_tokens>reminder after every turn, so the hoisted block grew the front of the prompt each turn and prefix-based caches reported a full cache write and zero cache reads on every turn (mid-conversation switch cache checkpoint #7145)reasoning.contextRejected the Request - Areasoning.contextvalue the target model does not accept is dropped on the OpenAI and Azure Responses path, soall_turnson the original gpt-5 family including gpt-5-pro, gpt-5.1 to gpt-5.3 and the o-series runs under the model's owncurrent_turndefault instead of failing withUnsupported value. gpt-5.4, gpt-5.5 and gpt-5.6 keep it. Accepted values come from the datasheet rowsupported_reasoning_contexts(turn reasoning context support #7140)ALTER TABLE ... DELETEmutations. The retention cleaner issued one per 100 rows, each rewriting the whole current-month part, and the once-a-minute stale-processingsweeps issued one per table unconditionally. Every delete is now a single lightweightDELETE FROM ... WHEREper run, skipped when nothing matches. The table TTL derived fromlogs_store.retention_daysis reconciled on every startup with a metadata-onlyMODIFY TTL, so changing the value reaches existing tables;0leaves an existing TTL untouched ([Bug]: ClickHouse logs store: retention cleaner runs oneALTER TABLE … DELETEmutation per 100 rows and fills replica disks #7098, adds clickhouse delte rows improvements #7103)UsageTracker.Cleanup()took its final budget and rate-limit snapshots before stopping the periodic reset worker, sotrackerCancel()could cancel an in-flight dump and fail withcontext canceled. The worker is now cancelled and awaited before the final dumps, a queued ticker event cannot start another reset cycle during shutdown, andcontext.Canceledis treated as expected only when the tracker context was actually cancelled ([Bug]: UsageTracker cleanup races the periodic rate-limit dump during shutdown #7099, fix: prevent governance cleanup dump race #7100) (thanks @Constantine3!)token_endpoint_auth_method: nonehave no client secret, and unconditionally settingclient_secret=in the refresh POST body sent an emptyclient_secret_postattempt. Strict authorization servers answeredinvalid_client, flipping the token row toneeds_reautheven though the refresh token was valid. The parameter is now omitted when the secret is empty, matching the PKCE code-exchange path (fix: omitclient_secretin refresh token grant when client has no secret #7042)ComplexityInputand fall back to classifying the recoveredLastUserText; the skip path applies only when that is also empty (fix(complexity): preserve input for continuation fallback classification when session state is absent #7122)v1oropenai/v1) and returns a 400 on the other. Hard-coded string matching in two packages covered only generations up to GPT-5 and Gemma 4, so GPT-6 and any future closed-generation model silently fell through to the wrong path. Resolution is centralized inResolveBedrockMantleBasePath, backed by abedrock_mantle_base_pathdatasheet field with family-name detection as the fallback, so a new generation needs a datasheet row rather than a code change (fix: dynamic mantle base path and gpt 6 reasoning helpers #7077)allowed_models: ["*"]Handling Reverted - The wildcard handling for governance virtual keys added in vk allowed models * handling for governance #6767 is reverted. Configurations relying onallowed_models: ["*"]with an empty synced catalog return to the previous behaviour (Revert "vk allowed models * handling for governance" #7053)perUserHeaderKeysNot Rendered -mcp.clientConfigs[].perUserHeaderKeysis mapped into the renderedconfig.json(Helm chart: mcp.clientConfigs[].perUserHeaderKeys not rendered into config.json #6033, fix: Helm chart - map perUserHeaderKeys into rendered config.json #6034) (thanks @CallumWayve!)access_profilesas an array in the Helm and config schemas. Bifrost supports multiple access profiles but the schemas accepted only the deprecated singularaccess_profile. The singular form keeps rendering unchanged, the plural wins when both are present, and an explicitly empty plural list clears existing grants (fix(helm): support multiple access profiles on governance roles #7044) (thanks @CarlosLanderas!)🗄️ Database Migrations
use_openai_endpointscolumn to the provider keys table for Bedrock OpenAI-compatible endpoint routing. Reversible: the rollback drops the added column. Additive and nullable, so it is safe to run during a rolling upgrade.off_peak_cost_multiplierandpeak_hourstogovernance_model_pricingfor time-of-day pricing. Reversible: the rollback drops both added columns. Additive and nullable, so it is safe to run during a rolling upgrade.mcp_tool_logs:user_name,team_name,customer_name,business_unit_name, theteam_ids/team_names,customer_ids/customer_namesandbusiness_unit_ids/business_unit_namespairs, plusbudget_idsandrate_limit_ids. Reversible: the rollback drops all twelve in reverse order. Additive and nullable, so it is safe to run during a rolling upgrade. The twelveALTER TABLEs run under a bounded DDL lock wait, so startup does not stall behind a long-running log transaction holdingACCESS EXCLUSIVEon a continuously written table.🐙 Closed GitHub Issues
default_request_timeout_in_secondsandstream_idle_timeout_in_secondsdo not fire while waiting for response headers, a silent upstream blocks the request until the upstream closes, and the fallback is never usedmax_retries, one abandoned request produced 10 upstream attempts over ~20 minutesALTER TABLE ... DELETEmutation per 100 rows and fills replica diskstool_search_tool_*/defer_loading), served eagerly over Converse