Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Run CLI tests
working-directory: cli
Expand Down Expand Up @@ -97,7 +97,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Configure Git
run: |
Expand Down
28 changes: 14 additions & 14 deletions .github/workflows/release-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -254,7 +254,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Docker Compose
run: |
Expand Down Expand Up @@ -356,7 +356,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Docker Compose
run: |
Expand Down Expand Up @@ -452,7 +452,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -519,7 +519,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -602,7 +602,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -693,7 +693,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -802,7 +802,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -897,7 +897,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -995,7 +995,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Configure Git
run: |
Expand Down Expand Up @@ -1114,7 +1114,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -1231,7 +1231,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -1295,7 +1295,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down Expand Up @@ -1364,7 +1364,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/snyk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Setup Go workspace
run: make setup-workspace
Expand Down Expand Up @@ -134,7 +134,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "1.26.2"
go-version: "1.26.1"

- name: Setup Go workspace
run: make setup-workspace
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

[![Go Report Card](https://goreportcard.com/badge/github.com/maximhq/bifrost/core)](https://goreportcard.com/report/github.com/maximhq/bifrost/core)
[![Discord badge](https://dcbadge.limes.pink/api/server/https://discord.gg/exN5KAydbU?style=flat)](https://discord.gg/exN5KAydbU)
[![Known Vulnerabilities](https://snyk.io/test/github/maximhq/bifrost/badge.svg)](https://snyk.io/test/github/maximhq/bifrost)
[![codecov](https://codecov.io/gh/maximhq/bifrost/branch/main/graph/badge.svg)](https://codecov.io/gh/maximhq/bifrost)
![Docker Pulls](https://img.shields.io/docker/pulls/maximhq/bifrost)
[<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 95px; height: 21px;">](https://app.getpostman.com/run-collection/31642484-2ba0e658-4dcd-49f4-845a-0c7ed745b916?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D31642484-2ba0e658-4dcd-49f4-845a-0c7ed745b916%26entityType%3Dcollection%26workspaceId%3D63e853c8-9aec-477f-909c-7f02f543150e)
Expand Down
2 changes: 1 addition & 1 deletion cli/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/cli

go 1.26.2
go 1.26.1

require (
github.com/bytedance/sonic v1.15.0
Expand Down
28 changes: 20 additions & 8 deletions core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -4689,8 +4689,16 @@ func (bifrost *Bifrost) tryStreamRequest(ctx *schemas.BifrostContext, req *schem
// shared processedResponse or processedError objects.
streamResponse := providerUtils.BuildClientStreamChunk(ctx, processedResponse, processedError)

// Send the processed message to the output stream
outputStream <- streamResponse
// Guarded send: if the consumer abandons outputStream (client
// disconnect, ctx cancel), drain the upstream shortCircuit.Stream
// so its producer can exit cleanly instead of blocking on its send.
select {
case outputStream <- streamResponse:
case <-ctx.Done():
for range shortCircuit.Stream {
}
return
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

//TODO: Release the processed response immediately after use
}
Expand Down Expand Up @@ -4952,7 +4960,7 @@ func executeRequestWithRetries[T any](
// the SSE stream instead of returning proper HTTP error status codes.
if bifrostError == nil {
if streamChan, ok := any(result).(chan *schemas.BifrostStreamChunk); ok {
checkedStream, drainDone, firstChunkErr := providerUtils.CheckFirstStreamChunkForError(streamChan)
checkedStream, drainDone, firstChunkErr := providerUtils.CheckFirstStreamChunkForError(ctx, streamChan)
if firstChunkErr != nil {
<-drainDone
bifrostError = firstChunkErr
Expand Down Expand Up @@ -5216,12 +5224,16 @@ func (bifrost *Bifrost) requestWorker(provider schemas.Provider, config *schemas
}
return resp, nil
}
// Store a finalizer callback to create aggregated post-hook spans at stream end
// This closure captures the pipeline reference and releases it after finalization
// Store a finalizer callback to create aggregated post-hook spans at stream end.
// Wrapped in sync.Once so the normal end-of-stream invocation and a deferred
// safety-net invocation (e.g. from a provider goroutine's panic path) cannot
// double-release the pipeline.
var finalizerOnce sync.Once
postHookSpanFinalizer := func(ctx context.Context) {
pipeline.FinalizeStreamingPostHookSpans(ctx)
// Release the pipeline AFTER finalizing spans (not before streaming completes)
bifrost.releasePluginPipeline(pipeline)
finalizerOnce.Do(func() {
pipeline.FinalizeStreamingPostHookSpans(ctx)
bifrost.releasePluginPipeline(pipeline)
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
req.Context.SetValue(schemas.BifrostContextKeyPostHookSpanFinalizer, postHookSpanFinalizer)
}
Expand Down
2 changes: 1 addition & 1 deletion core/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/core

go 1.26.2
go 1.26.1
Comment thread
greptile-apps[bot] marked this conversation as resolved.

require (
cloud.google.com/go v0.123.0
Expand Down
5 changes: 5 additions & 0 deletions core/providers/anthropic/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ func HandleAnthropicChatCompletionStreaming(

// Start streaming in a goroutine
go func() {
defer providerUtils.EnsureStreamFinalizerCalled(ctx)
defer func() {
model := "unknown"
if meta != nil {
Expand Down Expand Up @@ -1187,6 +1188,7 @@ func HandleAnthropicResponsesStream(

// Start streaming in a goroutine
go func() {
defer providerUtils.EnsureStreamFinalizerCalled(ctx)
defer func() {
model := "<unknown>"
if meta != nil {
Expand Down Expand Up @@ -1227,6 +1229,7 @@ func HandleAnthropicResponsesStream(
stopCancellation := providerUtils.SetupStreamCancellation(ctx, resp.BodyStream(), logger)
defer stopCancellation()


sseReader := providerUtils.GetSSEEventReader(ctx, reader)
chunkIndex := 0

Expand Down Expand Up @@ -2786,6 +2789,7 @@ func (provider *AnthropicProvider) PassthroughStream(

ch := make(chan *schemas.BifrostStreamChunk, schemas.DefaultStreamBufferSize)
go func() {
defer providerUtils.EnsureStreamFinalizerCalled(ctx)
defer func() {
if ctx.Err() == context.Canceled {
providerUtils.HandleStreamCancellation(ctx, postHookRunner, ch, provider.GetProviderKey(), req.Model, schemas.PassthroughStreamRequest, provider.logger)
Expand All @@ -2798,6 +2802,7 @@ func (provider *AnthropicProvider) PassthroughStream(
defer stopIdleTimeout()
defer stopCancellation()


buf := make([]byte, 4096)
for {
n, readErr := bodyStream.Read(buf)
Expand Down
12 changes: 10 additions & 2 deletions core/providers/anthropic/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,16 @@ func ToAnthropicChatRequest(ctx *schemas.BifrostContext, bifrostReq *schemas.Bif
// (3) Custom tool (tool.Custom != nil) — not currently forwarded
// to Anthropic; skipped.
if bifrostReq.Params.Tools != nil {
tools := make([]AnthropicTool, 0, len(bifrostReq.Params.Tools))
for _, tool := range bifrostReq.Params.Tools {
// Strip server tools the target provider doesn't support per
// ProviderFeatures (e.g. web_search on Vertex's non-supporting
// model variants, or MCP on Bedrock when this converter is used
// by non-Bedrock providers). Function/custom tools are always
// kept. The dropped set is discarded — "silent strip + continue"
// policy per user direction. See Bedrock's convertToolConfig for
// the direct-Bedrock-path equivalent.
filtered, _ := ValidateChatToolsForProvider(bifrostReq.Params.Tools, bifrostReq.Provider)
tools := make([]AnthropicTool, 0, len(filtered))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
for _, tool := range filtered {
if tool.Function != nil {
tools = append(tools, convertFunctionToolToAnthropic(tool))
continue
Expand Down
71 changes: 71 additions & 0 deletions core/providers/anthropic/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,77 @@ import (
"github.com/maximhq/bifrost/core/schemas"
)

// anthropicToolTypePrefixToFeature maps Anthropic server-tool type prefixes
// to the corresponding ProviderFeatureSupport flag. Mirrors the structure of
// betaHeaderPrefixToFeature (defined later in this file) so tool-type gating
// and beta-header gating share the same shape.
//
// Prefix-based so future version bumps (e.g. web_search_20261231) flow
// through without a code change. Exact-match types (currently just
// "mcp_toolset") are handled separately.
var anthropicToolTypePrefixToFeature = map[string]func(ProviderFeatureSupport) bool{
"web_search_": func(f ProviderFeatureSupport) bool { return f.WebSearch },
"web_fetch_": func(f ProviderFeatureSupport) bool { return f.WebFetch },
"code_execution_": func(f ProviderFeatureSupport) bool { return f.CodeExecution },
"computer_": func(f ProviderFeatureSupport) bool { return f.ComputerUse },
"bash_": func(f ProviderFeatureSupport) bool { return f.Bash },
"memory_": func(f ProviderFeatureSupport) bool { return f.Memory },
"text_editor_": func(f ProviderFeatureSupport) bool { return f.TextEditor },
"tool_search_tool_": func(f ProviderFeatureSupport) bool { return f.ToolSearch },
}

// isAnthropicServerToolSupported returns whether the given Anthropic server-tool
// type string is supported by the provider's ProviderFeatureSupport. Unknown
// types return true (forward-compat: let the provider reject if truly invalid
// rather than Bifrost dropping a tool Anthropic has just added).
func isAnthropicServerToolSupported(toolType string, features ProviderFeatureSupport) bool {
// Exact-match types first.
if toolType == "mcp_toolset" {
return features.MCP
}
// Prefix match for versioned types.
for prefix, check := range anthropicToolTypePrefixToFeature {
if strings.HasPrefix(toolType, prefix) {
return check(features)
}
}
return true
}

// ValidateChatToolsForProvider is the chat-path mirror of
// ValidateToolsForProvider. It partitions []schemas.ChatTool into a keep-set
// (function/custom tools + server tools supported on the target provider)
// and a dropped-set (server-tool Type strings the provider doesn't support
// per ProviderFeatures).
//
// Does NOT mutate its input. Callers decide the policy (silent strip vs
// fail-fast). The Bedrock ChatCompletion path uses silent strip so the
// request still reaches the provider without the unsupported tool; the model
// responds with a prose completion instead of tool use.
//
// Unknown providers keep all tools (safe default for custom providers),
// matching ValidateToolsForProvider.
func ValidateChatToolsForProvider(tools []schemas.ChatTool, provider schemas.ModelProvider) (keep []schemas.ChatTool, dropped []string) {
features, ok := ProviderFeatures[provider]
if !ok {
return tools, nil
}
for _, tool := range tools {
// Function/custom tools are universal — always keep.
if tool.Function != nil || tool.Custom != nil {
keep = append(keep, tool)
continue
}
t := string(tool.Type)
if isAnthropicServerToolSupported(t, features) {
keep = append(keep, tool)
} else {
dropped = append(dropped, t)
}
}
return keep, dropped
}

// ValidateToolsForProvider checks if all tools in the request are supported by the given provider.
// Returns an error for the first unsupported tool found.
func ValidateToolsForProvider(tools []schemas.ResponsesTool, provider schemas.ModelProvider) error {
Expand Down
Loading
Loading