Bifrost HTTP Transport Release v2.2.1
✨ Features
- Virtual Key Assignees and Expanded Search - The virtual key list resolves
assigned_userfor every row via a single batch lookup, and VK search matches team, customer and user names in addition to key fields, so keys can be found by who owns them (#7225, #7226) - User Attribution in Prometheus Metrics - The telemetry plugin's metrics export includes user id and user name labels, so per-user usage and error rates can be graphed and alerted on directly (#7267)
- Prompt Cache Breakpoints Capability - A
SupportsPromptCacheBreakpointsmodel cap with a name-based fallback for OpenRouter gatesprompt_cache_breakpointforwarding, so OpenRouter's non-Claude models stop rejecting requests that carry it (#7261, #7262) - MCP Observed Latency - Native (observed) MCP tool-call latency from
observed_latency_msis surfaced in the logs duration column and detail sheet, kept clearly distinct from true execution time and never fabricating a synthetic start timestamp on the timeline (#7271)
🐞 Fixed
- Resource ID Path Injection - Caller-supplied resource IDs (batch, file, video, container, response IDs, cached content names) were interpolated into outbound URL paths unvalidated; a crafted ID with
../,?,#, percent-encoded bytes or control characters could redirect the request to an unintended upstream endpoint. A centralEscapeResourceIDhelper validates and escapes every such ID (#7307, #7310) - Bedrock S3 SSRF - A caller-supplied
s3://file ID ors3_bucketparam could control the upstream host, since S3 virtual-hosted URLs are built ashttps://{bucket}.{s3host}/{key}; a bucket like127.0.0.1:PORT#opened a SigV4-signed TLS connection to a caller-chosen host. Bucket names are now validated with a DNS-compatible regex (#7315) - Caller-Forged Billing Idempotency - The billing-idempotency key was
(RequestID, AttemptNumber), andRequestIDmay come from the caller'sx-request-idheader, so two unrelated requests sharing a deliberately chosen ID collided and the second was silently never charged. An internally mintedBillingNonceis mixed into the key, making it unforgeable (#7303) - Abandoned Non-Streaming Request Hung Forever - The worker kept a
ctx.Done()arm on an already-claimed delivery send, a regression from the #6972 delivery fix, so a non-streaming caller could hang indefinitely (#7313) - Repeated Empty Thinking Blocks - Streaming chat chunks emitted empty reasoning/message fields on every content delta, which clients rendered as repeated empty thinking blocks (#7318)
- Claude Code Thread Continuations Failed Behind Key Rotation - Claude Code's server-side conversation threads are bound to the upstream account that creates them, and Bifrost's per-request key selection, retries and fallbacks cannot keep a continuation on that account, so
thread: {"type": "continue"}requests failed withthread_not_found(about half the time on a two-key config). The Anthropic integration now declares itself stateless: continuations are refused before the provider call with a 400 whosedetails.error_codeisthread_unsupported_request, which makes the client resend the turn in full and stop sending the thread field for the rest of the session, and the provider raw-body path stripsthreadfrom create requests so no orphaned thread state accumulates upstream. Token counting is never refused (#7274) - Gemini Flash-Lite Minimal Thinking Promoted to Low - Normalized
gemini-3.1-flash-literequests silently promotedminimalthinking tolowon both Gemini and Vertex; the text model's four supported levels are now registered, preserving the image variant's separate restrictions (#7288) (thanks @Javtor!) - Config.json Virtual Key Limits Broke Under UI Edits - VK rate limits and budgets created via the config.json standalone-limits flow and then edited through the UI produced duplicate, conflicting ownership records (standalone budgets owned directly by the VK, plus orphaned UUID rate-limit rows created by the UI). Migration
migrate_vk_standalone_limits_to_model_configsconsolidates ownership into VK-scoped model configs while preserving usage counters, and the write paths stop creating the divergent rows (#7291) - Bedrock Service Tier Rejections - Service tier forwarding for Bedrock (Converse and Mantle paths) sent whatever tier was requested; it is now gated on explicit model capability metadata, failing closed when none exists, so models that do not support the requested tier stop rejecting the request (#7266)
- Anthropic Root-Level Tool Schema Compositions - Anthropic rejects
oneOf/anyOf/allOfat the root of a tool'sinput_schemabut accepts them inside properties; root-level compositions are now rewritten into a flat object schema before dispatch, unblocking tools like Codex'sautomation_update(#7265) - GenAI Streaming TTS - Speech stream chunks routed through the
/genaiintegration had no converter registered;ToGeminiSpeechStreamResponsenow serves streaming TTS, and the streaming router returns a clean error instead of panicking when any stream converter is missing (#7248) - xAI Usage and Cost - xAI reports visible and reasoning completion tokens separately;
completion_tokensnow folds reasoning in soprompt_tokens + completion_tokens = total_tokensholds, and streaming cost normalization preserves xAI's authoritativecost_in_usd_ticksto the terminal usage chunk instead of falling back to catalog pricing (#7245, #7250) - OpenAI-Only
search_content_typesForwarded Everywhere -search_content_typesonweb_searchtools is an OpenAI-specific extension; it is now gated behind a per-provider capability check so Bedrock and other OpenAI-compatible backends receive a cleanweb_searchtool without the field (#7244) - Claude Code
diagnosticsField Rejected by Non-Native Providers - Claude Code sendsdiagnostics.previous_message_idon every request; on the typed-sanitizer path Bedrock, Vertex and Azure returned 400diagnostics: Extra inputs are not permitted. The field is stripped for providers that do not support it (#7243) - Unreadable Bedrock Error Logs - AWS returns errors in a flat
{"message": ..., "__type": ...}shape the shared Anthropic/OpenAI parsers never looked at, so Bedrock errors were logged with no human-readable reason; the root-level message now seedsBifrostError, and the logs UI falls back to showing the raw provider error body when no message could be extracted (#7221, #7222) - Anthropic
containerParam Dropped - The string-formcontainerparam on/anthropic/v1/messageswas silently dropped, so container reuse provisioned a fresh container every time; it is now carried through the round trip (#5829) (thanks @AdityaPainuli!) - Responses API Finish Reason Missing From OTEL Spans -
gen_ai.response.finish_reason/finish_reasonsare now emitted on thellm.callspan for/v1/responsesrequests, so refusals are visible to OTEL consumers, matching the chat path (#7205) (thanks @bkfl-notai!) - Cohere Fallback Response Shape - The Cohere-compatible route returned the raw Bifrost normalized response when a fallback served the request from a non-Cohere provider, which the Cohere SDK failed to parse; responses are now converted to the Cohere v2 shape (
message,finish_reason) (#7198) - Skill Serving Race - Fixed a race condition in the skill serving handler
- Log and Dashboard Label Truncation - Long model and provider labels in the logs table and dashboard charts truncate from the start so the distinctive suffix stays visible, the logs model column is wider, the search field border and icon spacing are cleaned up, and key picker options are keyed by id so duplicate labels stop highlighting together (#7215, #7216, #7217, #7269, #7297)
🗄️ Database Migrations
- migrate_vk_standalone_limits_to_model_configs - Consolidates virtual key standalone budgets and rate limits into VK-scoped model configs: standalone budgets are re-pointed to the VK's top-level model config (created if missing, usage preserved), orphaned duplicate UUID rate-limit rows created by the UI are deleted with the model config re-pointed to the canonical config.json row, and
vk.rate_limit_idis cleared.
🐙 Closed GitHub Issues
- #123 - Files API Support
- #5707 - string-form
containerparam on /anthropic/v1/messages is silently dropped - container reuse provisions a fresh container - #7204 - Responses API path never sets gen_ai.response.finish_reason in OTEL traces (refusals invisible to observability)
- #7287 - Gemini 3.1 Flash-Lite minimal thinking is silently promoted to low
- #7294 - Streaming chat chunks emit empty reasoning/message fields on every content delta, causing repeated thinking blocks
- #7308 - non-streaming caller hangs forever - worker keeps a ctx.Done() arm on a claimed delivery send (regression from #6972)
Installation
Docker
docker run -p 8080:8080 maximhq/bifrost:v2.2.1Binary Download
npx @maximhq/bifrost --transport-version v2.2.1Docker Images
maximhq/bifrost:v2.2.1- This specific versionmaximhq/bifrost:latest- Latest version (updated with this release)
This release was automatically created with dependencies: core v1.9.1, framework v1.7.1. All plugins have been validated and updated.