Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d305f61
[docs] : google model armor as guardrail provider docs (#3660)
Madhuvod May 21, 2026
1b11955
fix: removes manual setting of type custom for anthropic tools (#3652)
roroghost17 May 21, 2026
cb69abc
fix: fix ListModels for keyless providers (#3655)
roroghost17 May 21, 2026
a625094
feat: preserve time filter params when navigating between sidebar ite…
impoiler May 21, 2026
acdfec2
refactor: move auth check from login component to route loader with r…
impoiler May 21, 2026
6ea298d
feat: add team and bu filter support for dashboard and logs (#3650)
impoiler May 21, 2026
5db9b59
fix: map vertex trrafic type to bifrost service tier (#3662)
TejasGhatte May 21, 2026
12745e4
fix: fixes the start time setting for correct ttft metric value (#3668)
roroghost17 May 21, 2026
f5f29fc
fix: preserve tool call stop reason in Anthropic streaming fallback (…
dicnunz May 21, 2026
96dbf26
feat(governance): add virtual key blocked models (#3653)
Vaibhav701161 May 21, 2026
b9f5bff
table updates (#3665)
akshaydeo May 21, 2026
6979ea1
Revert "feat: add `access_profile_id` to virtual keys for direct acce…
BearTS May 21, 2026
44fecb9
chore: drop `access_profile_id` column from `governance_virtual_keys`…
BearTS May 21, 2026
598431d
test: add v1.5.3 migration test coverage for feature flags, temp toke…
BearTS May 21, 2026
d782a1f
adds created_by for virtual keys (#3672)
akshaydeo May 21, 2026
cd435c0
fix: ensure toasts remain clickable above modal overlays (#3674)
impoiler May 21, 2026
ffd74ad
fix: remove save/cancel icons and fix sheet layout growth in routing …
impoiler May 21, 2026
3c4d3ae
fix: make virtual keys table fill available height with sticky header…
impoiler May 21, 2026
73f6e97
fix idle timeout panic
akshaydeo May 21, 2026
3f07831
mcp log fingerprinting
akshaydeo May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
394 changes: 332 additions & 62 deletions .github/workflows/scripts/run-migration-tests.sh

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -1904,7 +1904,7 @@ run-provider-harness-test: $(if $(HELP),,install-newman) ## Run the Bifrost prov
fi; \
$(ECHO) "$(CYAN)Merging per-provider reports into tmp/newman-report.json...$(NC)"; \
if command -v jq >/dev/null 2>&1 && ls tmp/newman-report-*.json >/dev/null 2>&1; then \
jq -s '{collection: (.[0].collection // {}), environment: (.[0].environment // {}), run: {executions: [.[].run.executions[]?], failures: [.[].run.failures[]?], stats: {iterations: {total: 1, pending: 0, failed: 0}, items: {total: ([.[].run.stats.items.total // 0] | add)}, requests: {total: ([.[].run.stats.requests.total // 0] | add), failed: ([.[].run.stats.requests.failed // 0] | add)}}, timings: (.[0].run.timings // {})}}' tmp/newman-report-*.json > tmp/newman-report.json || $(ECHO) "$(YELLOW)Report merge failed; per-provider reports remain at tmp/newman-report-*.json$(NC)"; \
jq -s 'def failed: (((.assertions // []) | any(.error?)) or ((.response.code // 0) == 0) or ((.response.code // 0) >= 400) or (.response | not)); def trimstream: if (.response.stream.type? == "Buffer" and ((.response.stream.data // []) | length) > 20000) then (.response.stream.data = .response.stream.data[:20000] | .response.stream.truncated = true) else . end; def sanitize: if failed then trimstream else (.response.stream = null) end; {collection: (.[0].collection // {}), environment: (.[0].environment // {}), run: {executions: [.[].run.executions[]? | sanitize], failures: [.[].run.failures[]?], stats: {iterations: {total: 1, pending: 0, failed: 0}, items: {total: ([.[].run.stats.items.total // 0] | add)}, requests: {total: ([.[].run.stats.requests.total // 0] | add), failed: ([.[].run.stats.requests.failed // 0] | add)}}, timings: (.[0].run.timings // {})}}' tmp/newman-report-*.json > tmp/newman-report.json || $(ECHO) "$(YELLOW)Report merge failed; per-provider reports remain at tmp/newman-report-*.json$(NC)"; \
cat tmp/newman-cli-*.log > tmp/newman-cli.log 2>/dev/null || true; \
else \
$(ECHO) "$(YELLOW)jq not found or no reports produced; skipping merge. See tmp/newman-report-*.json$(NC)"; \
Expand Down
6 changes: 3 additions & 3 deletions core/providers/anthropic/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ func (provider *AnthropicProvider) ListModels(ctx *schemas.BifrostContext, keys
}
if provider.customProviderConfig != nil && provider.customProviderConfig.IsKeyLess {
return providerUtils.HandleKeylessListModelsRequest(schemas.Anthropic, func() (*schemas.BifrostListModelsResponse, *schemas.BifrostError) {
return provider.listModelsByKey(ctx, schemas.Key{}, request)
return provider.listModelsByKey(ctx, schemas.Key{Models: schemas.WhiteList{"*"}}, request)
})
}
return providerUtils.HandleMultipleListModelsRequests(
Expand Down Expand Up @@ -631,6 +631,7 @@ func HandleAnthropicChatCompletionStreaming(
// MaxResponseBodySize > 0 so ErrBodyTooLarge triggers StreamBody for Content-Length responses.
activeClient := providerUtils.PrepareResponseStreaming(ctx, client, resp)

startTime := time.Now()
// Make the request
err := activeClient.Do(req, resp)
if usedLargePayloadBody {
Expand Down Expand Up @@ -713,7 +714,6 @@ func HandleAnthropicChatCompletionStreaming(

chunkIndex := 0

startTime := time.Now()
lastChunkTime := startTime

// Track minimal state needed for response format
Expand Down Expand Up @@ -1103,6 +1103,7 @@ func HandleAnthropicResponsesStream(
// MaxResponseBodySize > 0 so ErrBodyTooLarge triggers StreamBody for Content-Length responses.
activeClient := providerUtils.PrepareResponseStreaming(ctx, client, resp)

startTime := time.Now()
// Make the request
err := activeClient.Do(req, resp)
if usedLargePayloadBody {
Expand Down Expand Up @@ -1186,7 +1187,6 @@ func HandleAnthropicResponsesStream(
sseReader := providerUtils.GetSSEEventReader(ctx, reader)
chunkIndex := 0

startTime := time.Now()
lastChunkTime := startTime

// Track minimal state needed for response format
Expand Down
91 changes: 91 additions & 0 deletions core/providers/anthropic/compaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,3 +701,94 @@ func TestToAnthropicResponsesStreamResponse_CompletedWithCompactionStopReason(t
t.Errorf("event[1] type = %v, want message_stop", messageStop.Type)
}
}

func TestMixedTextThenToolCallsStreamMapsStopReasonToToolUse(t *testing.T) {
t.Parallel()

ctx, cancel := schemas.NewBifrostContextWithCancel(context.Background())
defer cancel()

state := schemas.AcquireChatToResponsesStreamState()
defer schemas.ReleaseChatToResponsesStreamState(state)

content := "\n\nNow let me fetch the source branch:"
toolCallID := "chatcmpl-tool-123"
toolName := "Bash"
toolType := "function"
arguments := `{"command":"git fetch origin feat-IGPS-15746-controller-health-endpoint"}`
finishReason := string(schemas.BifrostFinishReasonToolCalls)

chatChunks := []*schemas.BifrostChatResponse{
{
Choices: []schemas.BifrostResponseChoice{
{
Index: 0,
ChatStreamResponseChoice: &schemas.ChatStreamResponseChoice{
Delta: &schemas.ChatStreamResponseChoiceDelta{Content: &content},
},
},
},
},
{
Choices: []schemas.BifrostResponseChoice{
{
Index: 0,
ChatStreamResponseChoice: &schemas.ChatStreamResponseChoice{
Delta: &schemas.ChatStreamResponseChoiceDelta{
ToolCalls: []schemas.ChatAssistantMessageToolCall{
{
Index: 0,
ID: &toolCallID,
Type: &toolType,
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: &toolName,
Arguments: arguments,
},
},
},
},
},
},
},
},
{
Choices: []schemas.BifrostResponseChoice{
{
Index: 0,
FinishReason: &finishReason,
ChatStreamResponseChoice: &schemas.ChatStreamResponseChoice{
Delta: &schemas.ChatStreamResponseChoiceDelta{},
},
},
},
},
}

var events []*AnthropicStreamEvent
for _, chunk := range chatChunks {
for _, responseEvent := range chunk.ToBifrostResponsesStreamResponse(state) {
events = append(events, ToAnthropicResponsesStreamResponse(ctx, responseEvent)...)
}
}

var messageDelta *AnthropicStreamEvent
var hasMessageStop bool
for _, event := range events {
if event.Type == AnthropicStreamEventTypeMessageDelta {
messageDelta = event
}
if event.Type == AnthropicStreamEventTypeMessageStop {
hasMessageStop = true
}
}

if messageDelta == nil || messageDelta.Delta == nil || messageDelta.Delta.StopReason == nil {
t.Fatalf("expected message_delta stop_reason in events: %#v", events)
}
if *messageDelta.Delta.StopReason != AnthropicStopReasonToolUse {
t.Fatalf("message_delta stop_reason = %q, want %q", *messageDelta.Delta.StopReason, AnthropicStopReasonToolUse)
}
if !hasMessageStop {
t.Fatal("expected message_stop event in mixed text+tool_use stream")
}
}
4 changes: 1 addition & 3 deletions core/providers/anthropic/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -5155,9 +5155,7 @@ func convertBifrostToolToAnthropic(model string, tool *schemas.ResponsesTool, pr
}
}

anthropicTool := &AnthropicTool{
Type: schemas.Ptr(AnthropicToolTypeCustom), // Custom tools require type: "custom"
}
anthropicTool := &AnthropicTool{}

if tool.Name != nil {
anthropicTool.Name = *tool.Name
Expand Down
2 changes: 1 addition & 1 deletion core/providers/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,7 @@ func (provider *AzureProvider) SpeechStream(ctx *schemas.BifrostContext, postHoo
req.SetBody(jsonBody)
}

startTime := time.Now()
// Make the request
requestErr := provider.client.Do(req, resp)
if requestErr != nil {
Expand Down Expand Up @@ -1073,7 +1074,6 @@ func (provider *AzureProvider) SpeechStream(ctx *schemas.BifrostContext, postHoo
defer stopCancellation()

chunkIndex := -1
startTime := time.Now()
lastChunkTime := startTime

// Read SSE events manually to handle binary data with embedded newlines
Expand Down
6 changes: 3 additions & 3 deletions core/providers/bedrock/bedrock.go
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,7 @@ func (provider *BedrockProvider) TextCompletionStream(ctx *schemas.BifrostContex
return nil, bifrostErr
}

startTime := time.Now()
resp, bifrostErr := provider.makeStreamingRequest(ctx, jsonData, key, request.Model, "invoke-with-response-stream")
if bifrostErr != nil {
return nil, providerUtils.EnrichError(ctx, bifrostErr, jsonData, nil, provider.sendBackRawRequest, provider.sendBackRawResponse)
Expand Down Expand Up @@ -964,7 +965,6 @@ func (provider *BedrockProvider) TextCompletionStream(ctx *schemas.BifrostContex
defer stopCancellation()

// Process AWS Event Stream format
startTime := time.Now()
decoder := eventstream.NewDecoder()
payloadBuf := make([]byte, 0, 1024*1024) // 1MB payload buffer

Expand Down Expand Up @@ -1166,6 +1166,7 @@ func (provider *BedrockProvider) ChatCompletionStream(ctx *schemas.BifrostContex
return nil, bifrostErr
}

startTime := time.Now()
resp, bifrostErr := provider.makeStreamingRequest(ctx, jsonData, key, request.Model, "converse-stream")
if bifrostErr != nil {
return nil, providerUtils.EnrichError(ctx, bifrostErr, jsonData, nil, provider.sendBackRawRequest, provider.sendBackRawResponse)
Expand Down Expand Up @@ -1204,7 +1205,6 @@ func (provider *BedrockProvider) ChatCompletionStream(ctx *schemas.BifrostContex
chunkIndex := 0

// Process AWS Event Stream format using proper decoder
startTime := time.Now()
lastChunkTime := startTime
decoder := eventstream.NewDecoder()
payloadBuf := make([]byte, 0, 1024*1024) // 1MB payload buffer
Expand Down Expand Up @@ -1546,6 +1546,7 @@ func (provider *BedrockProvider) ResponsesStream(ctx *schemas.BifrostContext, po
return nil, bifrostErr
}

startTime := time.Now()
resp, bifrostErr := provider.makeStreamingRequest(ctx, jsonData, key, request.Model, "converse-stream")
if bifrostErr != nil {
return nil, providerUtils.EnrichError(ctx, bifrostErr, jsonData, nil, provider.sendBackRawRequest, provider.sendBackRawResponse)
Expand Down Expand Up @@ -1598,7 +1599,6 @@ func (provider *BedrockProvider) ResponsesStream(ctx *schemas.BifrostContext, po
var isAccumulatingStructuredOutput bool

// Process AWS Event Stream format using proper decoder
startTime := time.Now()
lastChunkTime := startTime
decoder := eventstream.NewDecoder()
payloadBuf := make([]byte, 0, 1024*1024) // 1MB payload buffer
Expand Down
6 changes: 3 additions & 3 deletions core/providers/cohere/cohere.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func (provider *CohereProvider) ListModels(ctx *schemas.BifrostContext, keys []s
}
if provider.customProviderConfig != nil && provider.customProviderConfig.IsKeyLess {
return providerUtils.HandleKeylessListModelsRequest(provider.GetProviderKey(), func() (*schemas.BifrostListModelsResponse, *schemas.BifrostError) {
return provider.listModelsByKey(ctx, schemas.Key{}, request)
return provider.listModelsByKey(ctx, schemas.Key{Models: schemas.WhiteList{"*"}}, request)
})
}
return providerUtils.HandleMultipleListModelsRequests(
Expand Down Expand Up @@ -454,6 +454,7 @@ func (provider *CohereProvider) ChatCompletionStream(ctx *schemas.BifrostContext
req.SetBody(jsonBody)
}

startTime := time.Now()
// Make the request
err := provider.streamingClient.Do(req, resp)
if usedLargePayloadBody {
Expand Down Expand Up @@ -525,7 +526,6 @@ func (provider *CohereProvider) ChatCompletionStream(ctx *schemas.BifrostContext

sseReader := providerUtils.GetSSEDataReader(ctx, reader)
chunkIndex := 0
startTime := time.Now()
lastChunkTime := startTime

var responseID string
Expand Down Expand Up @@ -719,6 +719,7 @@ func (provider *CohereProvider) ResponsesStream(ctx *schemas.BifrostContext, pos
req.SetBody(jsonBody)
}

startTime := time.Now()
// Make the request
err := provider.streamingClient.Do(req, resp)
if usedLargePayloadBody {
Expand Down Expand Up @@ -792,7 +793,6 @@ func (provider *CohereProvider) ResponsesStream(ctx *schemas.BifrostContext, pos

chunkIndex := 0

startTime := time.Now()
lastChunkTime := startTime

// Create stream state for stateful conversions (outside loop to persist across events)
Expand Down
14 changes: 10 additions & 4 deletions core/providers/gemini/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,13 @@ func (response *GenerateContentResponse) ToBifrostChatResponse() *schemas.Bifros
// Set usage information
bifrostResp.Usage = ConvertGeminiUsageMetadataToChatUsage(response.UsageMetadata)

if response.UsageMetadata != nil && response.UsageMetadata.ServiceTier != "" {
tier := mapGeminiServiceTierToBifrost(response.UsageMetadata.ServiceTier)
bifrostResp.ServiceTier = &tier
if response.UsageMetadata != nil {
if t := mapGeminiTrafficTypeToBifrost(response.UsageMetadata.TrafficType); t != nil {
bifrostResp.ServiceTier = t
} else if response.UsageMetadata.ServiceTier != "" {
tier := mapGeminiServiceTierToBifrost(response.UsageMetadata.ServiceTier)
bifrostResp.ServiceTier = &tier
}
}

return bifrostResp
Expand Down Expand Up @@ -504,7 +508,9 @@ func (response *GenerateContentResponse) ToBifrostChatCompletionStream(state *Ge
// Add usage information if this is the last chunk
if isLastChunk && response.UsageMetadata != nil {
streamResponse.Usage = ConvertGeminiUsageMetadataToChatUsage(response.UsageMetadata)
if response.UsageMetadata.ServiceTier != "" {
if t := mapGeminiTrafficTypeToBifrost(response.UsageMetadata.TrafficType); t != nil {
streamResponse.ServiceTier = t
} else if response.UsageMetadata.ServiceTier != "" {
tier := mapGeminiServiceTierToBifrost(response.UsageMetadata.ServiceTier)
streamResponse.ServiceTier = &tier
}
Expand Down
10 changes: 5 additions & 5 deletions core/providers/gemini/gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func (provider *GeminiProvider) ListModels(ctx *schemas.BifrostContext, keys []s
}
if provider.customProviderConfig != nil && provider.customProviderConfig.IsKeyLess {
return providerUtils.HandleKeylessListModelsRequest(provider.GetProviderKey(), func() (*schemas.BifrostListModelsResponse, *schemas.BifrostError) {
return provider.listModelsByKey(ctx, schemas.Key{}, request)
return provider.listModelsByKey(ctx, schemas.Key{Models: schemas.WhiteList{"*"}}, request)
})
}
return providerUtils.HandleMultipleListModelsRequests(
Expand Down Expand Up @@ -425,6 +425,7 @@ func HandleGeminiChatCompletionStream(
req.SetBody(jsonBody)
}

startTime := time.Now()
// Make the request — caller is responsible for passing a streaming-configured client.
doErr := client.Do(req, resp)
if doErr != nil {
Expand Down Expand Up @@ -511,7 +512,6 @@ func HandleGeminiChatCompletionStream(
}

chunkIndex := 0
startTime := time.Now()
lastChunkTime := startTime

var responseID string
Expand Down Expand Up @@ -925,6 +925,7 @@ func HandleGeminiResponsesStream(
req.SetBody(jsonBody)
}

startTime := time.Now()
// Make the request — caller is responsible for passing a streaming-configured client.
doErr := client.Do(req, resp)
if doErr != nil {
Expand Down Expand Up @@ -1019,7 +1020,6 @@ func HandleGeminiResponsesStream(

chunkIndex := 0
sequenceNumber := 0 // Track sequence across all events
startTime := time.Now()
lastChunkTime := startTime

// Initialize stream state for responses lifecycle management
Expand Down Expand Up @@ -1414,6 +1414,7 @@ func (provider *GeminiProvider) SpeechStream(ctx *schemas.BifrostContext, postHo
req.SetBody(jsonBody)
}

startTime := time.Now()
// Make the request
err := provider.streamingClient.Do(req, resp)
if err != nil {
Expand Down Expand Up @@ -1485,7 +1486,6 @@ func (provider *GeminiProvider) SpeechStream(ctx *schemas.BifrostContext, postHo
sseReader := providerUtils.GetSSEDataReader(ctx, reader)
chunkIndex := -1
usage := &schemas.SpeechUsage{}
startTime := time.Now()
lastChunkTime := startTime

for {
Expand Down Expand Up @@ -1704,6 +1704,7 @@ func (provider *GeminiProvider) TranscriptionStream(ctx *schemas.BifrostContext,
req.SetBody(jsonBody)
}

startTime := time.Now()
// Make the request
err := provider.streamingClient.Do(req, resp)
if err != nil {
Expand Down Expand Up @@ -1773,7 +1774,6 @@ func (provider *GeminiProvider) TranscriptionStream(ctx *schemas.BifrostContext,
sseReader := providerUtils.GetSSEDataReader(ctx, reader)
chunkIndex := -1
usage := &schemas.TranscriptionUsage{}
startTime := time.Now()
lastChunkTime := startTime

var fullTranscriptionText string
Expand Down
Loading
Loading