Skip to content
Open
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: 2 additions & 0 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ jobs:
ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY }}
SGL_API_KEY: ${{ secrets.SGL_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
VERTEX_CREDENTIALS: ${{ secrets.VERTEX_CREDENTIALS }}
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/release-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ jobs:
allowed-endpoints: >
api.anthropic.com:443
api.cerebras.ai:443
api.cloudflare.com:443
api.cohere.ai:443
api.elevenlabs.io:443
api.fireworks.ai:443
Expand Down Expand Up @@ -221,6 +222,8 @@ jobs:
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
SGL_API_KEY: ${{ secrets.SGL_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
VERTEX_CREDENTIALS: ${{ secrets.VERTEX_CREDENTIALS }}
Expand Down Expand Up @@ -347,6 +350,8 @@ jobs:
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
SGL_API_KEY: ${{ secrets.SGL_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
VERTEX_CREDENTIALS: ${{ secrets.VERTEX_CREDENTIALS }}
Expand Down Expand Up @@ -449,6 +454,8 @@ jobs:
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
SGL_API_KEY: ${{ secrets.SGL_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
VERTEX_CREDENTIALS: ${{ secrets.VERTEX_CREDENTIALS }}
Expand Down Expand Up @@ -613,6 +620,7 @@ jobs:
172.38.0.2:5432
api.anthropic.com:443
api.cerebras.ai:443
api.cloudflare.com:443
api.cohere.ai:443
api.elevenlabs.io:443
api.github.com:443
Expand Down Expand Up @@ -692,6 +700,8 @@ jobs:
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
PARASAIL_API_KEY: ${{ secrets.PARASAIL_API_KEY }}
ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY }}
Expand Down Expand Up @@ -733,6 +743,7 @@ jobs:
allowed-endpoints: >
api.anthropic.com:443
api.cerebras.ai:443
api.cloudflare.com:443
api.cohere.ai:443
api.elevenlabs.io:443
api.github.com:443
Expand Down Expand Up @@ -802,6 +813,8 @@ jobs:
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
PARASAIL_API_KEY: ${{ secrets.PARASAIL_API_KEY }}
ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY }}
Expand Down Expand Up @@ -843,6 +856,7 @@ jobs:
allowed-endpoints: >
api.anthropic.com:443
api.cerebras.ai:443
api.cloudflare.com:443
api.cohere.ai:443
api.elevenlabs.io:443
api.github.com:443
Expand Down Expand Up @@ -912,6 +926,8 @@ jobs:
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
PARASAIL_API_KEY: ${{ secrets.PARASAIL_API_KEY }}
ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY }}
Expand Down Expand Up @@ -1021,6 +1037,8 @@ jobs:
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
SGL_API_KEY: ${{ secrets.SGL_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
VERTEX_CREDENTIALS: ${{ secrets.VERTEX_CREDENTIALS }}
Expand Down Expand Up @@ -1130,6 +1148,8 @@ jobs:
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
SGL_API_KEY: ${{ secrets.SGL_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
VERTEX_CREDENTIALS: ${{ secrets.VERTEX_CREDENTIALS }}
Expand Down Expand Up @@ -1255,6 +1275,8 @@ jobs:
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
SGL_API_KEY: ${{ secrets.SGL_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
VERTEX_CREDENTIALS: ${{ secrets.VERTEX_CREDENTIALS }}
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/scripts/test-docker-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ cat > "$CONFIG_FILE" << 'CONFIGEOF'
"keys": [{ "name": "Cerebras API Key", "value": "env.CEREBRAS_API_KEY", "weight": 1 }],
"network_config": { "default_request_timeout_in_seconds": 300 }
},
"cloudflare": {
"keys": [{ "name": "Cloudflare API Key", "value": "env.CLOUDFLARE_API_KEY", "weight": 1 }],
"network_config": { "base_url": "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/ai", "default_request_timeout_in_seconds": 300 }
},
"openrouter": {
"keys": [{ "name": "OpenRouter API Key", "value": "env.OPENROUTER_API_KEY", "weight": 1 }],
"network_config": { "default_request_timeout_in_seconds": 300 }
Expand Down Expand Up @@ -218,6 +222,16 @@ cat > "$CONFIG_FILE" << 'CONFIGEOF'
}
CONFIGEOF

# The heredoc above is single-quoted, which is correct for `env.XXX` strings
# (those are resolved by Bifrost at runtime, not by the shell). The Cloudflare
# base_url is the one exception because it's a plain string field that needs
# the runtime account id substituted in. Do that here so it doesn't matter
# whether the heredoc is single- or double-quoted.
if [ -n "${CLOUDFLARE_ACCOUNT_ID:-}" ]; then
# Use a non-/ delimiter for sed so the URL's slashes don't need escaping.
sed -i.bak "s|\$CLOUDFLARE_ACCOUNT_ID|${CLOUDFLARE_ACCOUNT_ID}|g" "$CONFIG_FILE" && rm -f "$CONFIG_FILE.bak"
fi

echo "Config file created at: $CONFIG_FILE"

# Run the Bifrost container connected to the docker-compose network
Expand All @@ -244,6 +258,8 @@ docker run -d \
-e GROQ_API_KEY="${GROQ_API_KEY:-}" \
-e PERPLEXITY_API_KEY="${PERPLEXITY_API_KEY:-}" \
-e CEREBRAS_API_KEY="${CEREBRAS_API_KEY:-}" \
-e CLOUDFLARE_API_KEY="${CLOUDFLARE_API_KEY:-}" \
-e CLOUDFLARE_ACCOUNT_ID="${CLOUDFLARE_ACCOUNT_ID:-}" \
-e OPENROUTER_API_KEY="${OPENROUTER_API_KEY:-}" \
-e PARASAIL_API_KEY="${PARASAIL_API_KEY:-}" \
-e AZURE_API_KEY="${AZURE_API_KEY:-}" \
Expand Down
3 changes: 3 additions & 0 deletions core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/maximhq/bifrost/core/providers/azure"
"github.com/maximhq/bifrost/core/providers/bedrock"
"github.com/maximhq/bifrost/core/providers/cerebras"
"github.com/maximhq/bifrost/core/providers/cloudflare"
"github.com/maximhq/bifrost/core/providers/cohere"
"github.com/maximhq/bifrost/core/providers/elevenlabs"
"github.com/maximhq/bifrost/core/providers/fireworks"
Expand Down Expand Up @@ -3900,6 +3901,8 @@ func (bifrost *Bifrost) createBaseProvider(providerKey schemas.ModelProvider, co
return perplexity.NewPerplexityProvider(config, bifrost.logger)
case schemas.Cerebras:
return cerebras.NewCerebrasProvider(config, bifrost.logger)
case schemas.Cloudflare:
return cloudflare.NewCloudflareProvider(config, bifrost.logger)
case schemas.Gemini:
return gemini.NewGeminiProvider(config, bifrost.logger), nil
case schemas.OpenRouter:
Expand Down
7 changes: 7 additions & 0 deletions core/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- fix: idle timeout panic in the streaming idle-timeout reader
- fix: short-circuit `IdleTimeoutReader` reads when the connection is already closed (#3672)
- fix: preserve tool call stop reason in Anthropic streaming fallback (#3640) (thanks [@dicnunz](https://github.com/dicnunz)!)
- fix: correct start-time setting for accurate TTFT metric value (#3668)
- fix: map Vertex traffic type to Bifrost service tier (#3662)
- fix: ListModels for keyless providers (#3655)
- fix: remove manual `type: custom` for Anthropic tools (#3652)
32 changes: 32 additions & 0 deletions core/internal/llmtests/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func (account *ComprehensiveTestAccount) GetConfiguredProviders() ([]schemas.Mod
schemas.Elevenlabs,
schemas.Perplexity,
schemas.Cerebras,
schemas.Cloudflare,
schemas.Gemini,
schemas.OpenRouter,
schemas.HuggingFace,
Expand Down Expand Up @@ -431,6 +432,15 @@ func (account *ComprehensiveTestAccount) GetKeysForProvider(ctx context.Context,
UseForBatchAPI: bifrost.Ptr(true),
},
}, nil
case schemas.Cloudflare:
return []schemas.Key{
{
Value: *schemas.NewEnvVar("env.CLOUDFLARE_API_KEY"),
Models: []string{"*"},
Weight: 1.0,
UseForBatchAPI: bifrost.Ptr(true),
},
}, nil
case schemas.Gemini:
return []schemas.Key{
{
Expand Down Expand Up @@ -737,6 +747,28 @@ func (account *ComprehensiveTestAccount) GetConfigForProvider(providerKey schema
BufferSize: 10,
},
}, nil
case schemas.Cloudflare:
// Workers AI's OpenAI-compat URL embeds the account id, so the test
// account composes BaseURL from CLOUDFLARE_ACCOUNT_ID. The provider
// keeps the base URL at `/ai` and appends `/v1/...` per request, so
// the trailing `/v1` is intentionally NOT included here — adding it
// would produce `…/ai/v1/v1/chat/completions` and 404 every call.
// When the env var is unset, NewCloudflareProvider returns an error
// and the gated TestCloudflare in cloudflare_test.go skips before
// reaching here.
return &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: fmt.Sprintf("https://api.cloudflare.com/client/v4/accounts/%s/ai", os.Getenv("CLOUDFLARE_ACCOUNT_ID")),
DefaultRequestTimeoutInSeconds: 120,
MaxRetries: 10,
RetryBackoffInitial: 5 * time.Second,
RetryBackoffMax: 3 * time.Minute,
},
ConcurrencyAndBufferSize: schemas.ConcurrencyAndBufferSize{
Concurrency: Concurrency,
BufferSize: 10,
},
}, nil
case schemas.VLLM:
return &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
Expand Down
34 changes: 34 additions & 0 deletions core/providers/cloudflare/cachedcontents.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cloudflare

import (
providerUtils "github.com/maximhq/bifrost/core/providers/utils"
"github.com/maximhq/bifrost/core/schemas"
)

// CachedContentCreate is unsupported on CloudflareProvider. Only Gemini and Vertex AI
// implement the cached-content lifecycle (Google AI Studio + Vertex AI named
// caches). Other providers either lack named cache management entirely or
// handle caching implicitly via per-message cache_control markers.
func (provider *CloudflareProvider) CachedContentCreate(ctx *schemas.BifrostContext, key schemas.Key, request *schemas.BifrostCachedContentCreateRequest) (*schemas.BifrostCachedContentCreateResponse, *schemas.BifrostError) {
return nil, providerUtils.NewUnsupportedOperationError(schemas.CachedContentCreateRequest, provider.GetProviderKey())
}

// CachedContentList is unsupported on CloudflareProvider (see CachedContentCreate).
func (provider *CloudflareProvider) CachedContentList(ctx *schemas.BifrostContext, keys []schemas.Key, request *schemas.BifrostCachedContentListRequest) (*schemas.BifrostCachedContentListResponse, *schemas.BifrostError) {
return nil, providerUtils.NewUnsupportedOperationError(schemas.CachedContentListRequest, provider.GetProviderKey())
}

// CachedContentRetrieve is unsupported on CloudflareProvider (see CachedContentCreate).
func (provider *CloudflareProvider) CachedContentRetrieve(ctx *schemas.BifrostContext, keys []schemas.Key, request *schemas.BifrostCachedContentRetrieveRequest) (*schemas.BifrostCachedContentRetrieveResponse, *schemas.BifrostError) {
return nil, providerUtils.NewUnsupportedOperationError(schemas.CachedContentRetrieveRequest, provider.GetProviderKey())
}

// CachedContentUpdate is unsupported on CloudflareProvider (see CachedContentCreate).
func (provider *CloudflareProvider) CachedContentUpdate(ctx *schemas.BifrostContext, keys []schemas.Key, request *schemas.BifrostCachedContentUpdateRequest) (*schemas.BifrostCachedContentUpdateResponse, *schemas.BifrostError) {
return nil, providerUtils.NewUnsupportedOperationError(schemas.CachedContentUpdateRequest, provider.GetProviderKey())
}

// CachedContentDelete is unsupported on CloudflareProvider (see CachedContentCreate).
func (provider *CloudflareProvider) CachedContentDelete(ctx *schemas.BifrostContext, keys []schemas.Key, request *schemas.BifrostCachedContentDeleteRequest) (*schemas.BifrostCachedContentDeleteResponse, *schemas.BifrostError) {
return nil, providerUtils.NewUnsupportedOperationError(schemas.CachedContentDeleteRequest, provider.GetProviderKey())
}
Loading