Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
dc73564
feat: add granular RBAC checks for API keys, inference, metrics, and …
impoiler May 8, 2026
69555e6
fix: hide delete log button instead of disabling it when user lacks d…
impoiler May 8, 2026
e250398
feat: add `MCPLogs` RBAC resource and enforce access control on MCP l…
impoiler May 8, 2026
9b62156
fix: replace unsafe inline jsonb cast with `bifrost_safe_jsonb` PL/pg…
impoiler May 12, 2026
2c6bd6d
feat: add required headers input to prompt playground settings panel …
impoiler May 12, 2026
0c13600
fix: skip pagination clamp for virtual keys export requests (#3416)
impoiler May 12, 2026
6caffa5
chore: bump `@maximhq/bifrost` to v1.6.3 (#3417)
impoiler May 12, 2026
8c37ed1
feat: add volume histogram chart to MCP logs page and fix drag-select…
impoiler May 12, 2026
5b54832
refactor: semantic cache plugin (#3210)
Pratham-Mishra04 May 12, 2026
fe6eea8
feat: remove `cleanup_on_shutdown` from semantic cache plugin config …
Pratham-Mishra04 May 12, 2026
a532cf7
refactor: semantic cache ui revamp (#3331)
Pratham-Mishra04 May 12, 2026
8216bd0
fix: resolve cache plugin at request time to support post-boot loads …
Pratham-Mishra04 May 12, 2026
7df5e38
fix: decouple cache telemetry from write decision and guard no-op sea…
Pratham-Mishra04 May 12, 2026
8e4684a
test: add semantic cache e2e test suite skeleton (#3425)
Pratham-Mishra04 May 12, 2026
b71899d
test: add direct cache e2e test suite (#3426)
Pratham-Mishra04 May 12, 2026
8f8e108
test: add semantic cache e2e test suite (#3427)
Pratham-Mishra04 May 12, 2026
335be6a
test: add semantic cache plugin lifecycle tests (#3428)
Pratham-Mishra04 May 12, 2026
f9cfe36
feat: add `test-semantic-cache` and `test-semantic-cache-complete` Ma…
Pratham-Mishra04 May 12, 2026
06eb289
harness improvements (#3457)
akshaydeo May 13, 2026
4fccacb
makefile diff fixes (#3462)
akshaydeo May 13, 2026
c4a01bc
Preserve Anthropic output schema refs (#3449)
Javtor May 13, 2026
c3cb27a
feat: use the new parameter json schema compliant to json schema spec…
BearTS May 14, 2026
9b98959
feat: replace log delete button with actions dropdown menu and pin ac…
impoiler May 14, 2026
fd1f6a5
fix: constrain model catalog table column widths and truncate overflo…
impoiler May 14, 2026
ca77d2a
fix: constrain provider keys table column widths and truncate long ke…
impoiler May 14, 2026
be8c682
feat: replace inline edit/delete buttons with dropdown menu in model …
impoiler May 14, 2026
a393ff9
feat: replace routing rule action buttons with dropdown menu (#3484)
impoiler May 14, 2026
d1f342a
feat: replace inline edit/delete buttons with dropdown menu in pricin…
impoiler May 14, 2026
49ce7c6
feat: replace inline action buttons with dropdown menu and pin action…
impoiler May 14, 2026
f2a270b
feat: replace inline action buttons with pinned dropdown menus and ad…
impoiler May 14, 2026
9a83fde
chore: `/ui` code formatting (#3494)
impoiler May 14, 2026
00cddd2
send last n messages helm upgrade (#3490)
akshaydeo May 14, 2026
b1a7d70
fix: wrap Makefile subshell cd commands in parentheses (#3333)
danpiths May 14, 2026
d9edc3d
feat: add Azure realtime provider and nested model normalization (#3334)
danpiths May 14, 2026
11f11a0
feat: enrich realtime routing, logging, cost, and session tracking (#…
danpiths May 14, 2026
a50c8e9
feat: improve realtime log detail UI with voice, transport, and audio…
danpiths May 14, 2026
23f64ce
fix: max tokens and thinking budget value (#3498)
TejasGhatte May 14, 2026
4837b8c
fix: include blob fields of azure in batch responses (#3469)
TejasGhatte May 14, 2026
63c4654
feat: add animated totals/averages to dashboard chart card headers (#…
impoiler May 14, 2026
08d8f7d
fix: set idle stream timeouts in streaming requests (#3495)
TejasGhatte May 14, 2026
359fb6e
feat: add tooltip with full precision values to chart card totals and…
impoiler May 14, 2026
fff90c2
fix: trim trailing whitespaces for anthropic and bedrock anthropic pr…
sammaji May 14, 2026
8d195db
harness updates (#3466)
akshaydeo May 14, 2026
9a1af9d
[docs]: docs for patronus ai guardrail provider (#3508)
Madhuvod May 14, 2026
4b61b2c
feat: add claude skill to validate API docs / openapi schema correctn…
roroghost17 May 14, 2026
fe0ce57
chore: update openapi files for missing docs on api endpoints (#3410)
roroghost17 May 14, 2026
fedacb2
feat: adds support for custom selection of plugins for otel trace spa…
roroghost17 May 15, 2026
91131be
streaming calls support for hanress (#3507)
akshaydeo May 15, 2026
8726002
broker mode schema changes (#3509)
akshaydeo May 15, 2026
4b4c916
broker docs (#3515)
akshaydeo May 15, 2026
52bc07b
fix: adds prefill message handling for responses in bedrock (#3517)
sammaji May 15, 2026
9460264
feat: updates playwright config to support enterprise ui tests and al…
sammaji May 15, 2026
3c97739
fix: updates e2e ui tests for virtual keys management (#3460)
sammaji May 15, 2026
2b6b680
fix: updates e2e ui tests for provider management (#3463)
sammaji May 15, 2026
65809e4
fix: adds e2e tests for mcp headers auth, oauth and per-user oauth (#…
sammaji May 15, 2026
81197c1
[feat]: use chat completions for openai custom providers that disable…
kevinpdev May 15, 2026
aa26bbc
test: update union type test to verify `parametersJsonSchema` passthr…
BearTS May 15, 2026
5322c8a
feat: add Bedrock Mantle inference engine support for `gpt-oss` model…
BearTS May 15, 2026
ace5b1c
fix: broaden Red Hat registry allowlist from `registry.access.redhat.…
BearTS May 15, 2026
1f39a81
fix: dont pass multipart request bodies in enrich error (#3524)
TejasGhatte May 15, 2026
55c17a0
fix: send missing bedrock lifecycle events (#3527)
TejasGhatte May 15, 2026
e5b8a68
fix: bedrock stop reason (#3506)
TejasGhatte May 15, 2026
abddb6c
handle ctx cancel before handling read errors in streaming (#3522)
akshaydeo May 15, 2026
d507c01
test case fixes (#3525)
akshaydeo May 15, 2026
d051793
remove budget level calendar alignemnt (#3434)
akshaydeo May 15, 2026
07f27b1
fix: fixes calendar_aligned migration and UI for VK (#3452)
roroghost17 May 15, 2026
aa0c652
feat: implements calendar align feature at team level (#3476)
roroghost17 May 15, 2026
d364a7b
feat: bedrock system tools (#3435)
TejasGhatte May 15, 2026
92ad762
[fix]: openai provider - add usage to completed event in responses to…
kevinpdev May 15, 2026
df0effe
codeeditor changes (#3529)
akshaydeo May 15, 2026
fa9acbe
fix: preserve OpenAI responses stream metadata (#3528)
etnperlong May 15, 2026
753c8eb
[fix]: add missing padding to provider api structure form (#3513)
d3lm May 15, 2026
ffa1888
fix: guard semantic cache `Cleanup` with `sync.Once` to prevent doubl…
Pratham-Mishra04 May 15, 2026
46e156f
fix: preserve OpenAI responses stream metadata (#3530)
akshaydeo May 15, 2026
218e73d
fixes alias in migration for team calendar aligned fixes (#3535)
akshaydeo May 15, 2026
18c4207
[image] : crowdstrike transparent logo (#3538)
Madhuvod May 15, 2026
4fdb7c7
sidebar ux improvement when collapsed (#3539)
akshaydeo May 15, 2026
ef7759f
fix(ui): validate OAuth popup messages (#2615)
binbandit May 16, 2026
e588891
adds missing cancel() to integration router (#3541)
akshaydeo May 16, 2026
d4924b1
[docs] : docs for crowdstrike aidr as provider (#3540)
Madhuvod May 16, 2026
ee0f04e
doc: fix some typos in comment
cuoguojida May 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
676 changes: 676 additions & 0 deletions .claude/skills/api-validator/SKILL.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .claude/skills/docs-writer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ grep -n 'func.*create\|func.*update\|func.*delete\|func.*get' transports/bifrost
| `plugins.go` | `/api/plugins` | CRUD plugins |
| `config.go` | `/api/config` | GET/PUT config |
| `config.go` | `/api/proxy-config` | GET/PUT proxy config |
| `cache.go` | `/api/cache/clear/{requestId}` | DELETE cache |
| `cache.go` | `/api/cache/clear/{cacheId}` | DELETE cache |
| `session.go` | `/api/session/*` | Login/logout/auth check |
| `oauth2.go` | `/api/oauth/*` | OAuth callback/status |

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1771,7 +1771,7 @@ jobs:
production.cloudflare.docker.com:443
proxy.golang.org:443
registry-1.docker.io:443
registry.access.redhat.com:443
*.redhat.com:443
*.quay.io:443
registry.npmjs.org:443
storage.googleapis.com:443
Expand Down Expand Up @@ -1862,7 +1862,7 @@ jobs:
production.cloudflare.docker.com:443
proxy.golang.org:443
registry-1.docker.io:443
registry.access.redhat.com:443
*.redhat.com:443
*.quay.io:443
registry.npmjs.org:443
storage.googleapis.com:443
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/scripts/validate-helm-config-fields.sh
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,6 @@ bifrost:
cache_by_model: true
cache_by_provider: false
exclude_system_prompt: true
cleanup_on_shutdown: true
vector_store_namespace: "bifrost-cache"
otel:
enabled: true
Expand Down Expand Up @@ -710,7 +709,6 @@ assert_field_value 'plugins: semantic_cache conversation_history_threshold' '.pl
assert_field_value 'plugins: semantic_cache cache_by_model' '.plugins.[4].config.cache_by_model' 'true'
assert_field_value 'plugins: semantic_cache cache_by_provider' '.plugins.[4].config.cache_by_provider' 'false'
assert_field_value 'plugins: semantic_cache exclude_system_prompt' '.plugins.[4].config.exclude_system_prompt' 'true'
assert_field_value 'plugins: semantic_cache cleanup_on_shutdown' '.plugins.[4].config.cleanup_on_shutdown' 'true'
assert_field_value 'plugins: semantic_cache vector_store_namespace' '.plugins.[4].config.vector_store_namespace' '"bifrost-cache"'

# OTEL plugin
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.env
.vscode
.DS_Store
.tool-versions
*_creds*
**/venv/
**/__pycache__/**
Expand Down Expand Up @@ -45,6 +46,7 @@ transports/schema/config.schema.json
*.db
*.db-shm
*.db-wal
transports/bifrost-http/v1.5.x

# Test reports
test-reports
Expand Down Expand Up @@ -173,3 +175,7 @@ ui/app/routeTree.gen.ts
.next

.infisical

# e2e test artifacts
examples/mcps/auth-demo-server/auth-demo-server
examples/mcps/oauth-demo-server/oauth-demo-server
299 changes: 256 additions & 43 deletions Makefile

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -4051,6 +4051,31 @@ func (bifrost *Bifrost) SelectKeyForProviderRequestType(ctx *schemas.BifrostCont
return bifrost.keySelector(ctx, supportedKeys, providerKey, model)
}

// ComputeRawStorageForProvider determines whether raw request/response payloads should be
// captured and stored in log records for the given provider. This is the same computation
// performed inside executeRequest (lines 5675-5713), exported for callers that bypass
// the normal inference path (e.g. realtime WebSocket/WebRTC sessions).
func (bifrost *Bifrost) ComputeRawStorageForProvider(ctx *schemas.BifrostContext, providerKey schemas.ModelProvider) bool {
if ctx == nil {
ctx = bifrost.ctx
}
if ctx == nil {
return false
}
config, err := bifrost.account.GetConfigForProvider(providerKey)
if err != nil || config == nil {
return false
}
effectiveStore := config.StoreRawRequestResponse
allowStorageOverride, _ := ctx.Value(schemas.BifrostContextKeyAllowPerRequestStorageOverride).(bool)
if allowStorageOverride {
if override, ok := ctx.Value(schemas.BifrostContextKeyStoreRawRequestResponse).(bool); ok {
effectiveStore = override
}
}
return effectiveStore
}

// WSStreamHooks holds the post-hook runner and cleanup function returned by RunStreamPreHooks.
// Call PostHookRunner for each streaming chunk, setting StreamEndIndicator on the final chunk.
// Call Cleanup when done to release the pipeline back to the pool.
Expand Down
2 changes: 2 additions & 0 deletions core/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[fix]: openai provider - add usage to completed event in responses to chat completions fallback [@kevinpdev](https://github.com/kevinpdev)
[feat]: use chat completions for openai custom providers that disable responses [@kevinpdev](https://github.com/kevinpdev)
5 changes: 4 additions & 1 deletion core/internal/llmtests/realtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ func RunRealtimeTest(t *testing.T, client *bifrost.Bifrost, ctx context.Context,
}

wsURL := rtProvider.RealtimeWebSocketURL(key, testConfig.RealtimeModel)
hdrs := rtProvider.RealtimeHeaders(key)
hdrs, headerErr := rtProvider.RealtimeHeaders(bfCtx, key)
if headerErr != nil {
t.Fatalf("failed to build realtime headers for provider %s: %v", testConfig.Provider, headerErr)
}

httpHeaders := http.Header{}
for k, v := range hdrs {
Expand Down
36 changes: 23 additions & 13 deletions core/providers/anthropic/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,6 @@ func (provider *AnthropicProvider) ChatCompletionStream(ctx *schemas.BifrostCont
headers["x-api-key"] = key.Value.GetValue()
}

providerUtils.SetStreamIdleTimeoutIfEmpty(ctx, provider.networkConfig.StreamIdleTimeoutInSeconds)

// Use shared Anthropic streaming logic
return HandleAnthropicChatCompletionStreaming(
ctx,
Expand All @@ -574,6 +572,7 @@ func (provider *AnthropicProvider) ChatCompletionStream(ctx *schemas.BifrostCont
jsonData,
headers,
provider.networkConfig.ExtraHeaders,
provider.networkConfig.StreamIdleTimeoutInSeconds,
provider.networkConfig.BetaHeaderOverrides,
providerUtils.ShouldSendBackRawRequest(ctx, provider.sendBackRawRequest),
providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse),
Expand All @@ -594,6 +593,7 @@ func HandleAnthropicChatCompletionStreaming(
jsonBody []byte,
headers map[string]string,
extraHeaders map[string]string,
streamIdleTimeoutInSeconds int,
betaHeaderOverrides map[string]bool,
sendBackRawRequest bool,
sendBackRawResponse bool,
Expand All @@ -603,6 +603,7 @@ func HandleAnthropicChatCompletionStreaming(
logger schemas.Logger,
postHookSpanFinalizer func(context.Context),
) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
providerUtils.SetStreamIdleTimeoutIfEmpty(ctx, streamIdleTimeoutInSeconds)
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
resp.StreamBody = true // Initialize for streaming
Expand Down Expand Up @@ -636,7 +637,7 @@ func HandleAnthropicChatCompletionStreaming(
providerUtils.DrainLargePayloadRemainder(ctx)
}
if err != nil {
defer providerUtils.ReleaseStreamingResponse(resp)
defer providerUtils.ReleaseStreamingResponse(ctx, resp)
if errors.Is(err, context.Canceled) {
return nil, providerUtils.EnrichError(ctx, &schemas.BifrostError{
IsBifrostError: false,
Expand All @@ -658,7 +659,7 @@ func HandleAnthropicChatCompletionStreaming(

// Check for HTTP errors
if resp.StatusCode() != fasthttp.StatusOK {
defer providerUtils.ReleaseStreamingResponse(resp)
defer providerUtils.ReleaseStreamingResponse(ctx, resp)
return nil, providerUtils.EnrichError(ctx, parseAnthropicError(resp), jsonBody, nil, sendBackRawRequest, sendBackRawResponse)
}

Expand All @@ -683,7 +684,7 @@ func HandleAnthropicChatCompletionStreaming(
}
close(responseChan)
}()
defer providerUtils.ReleaseStreamingResponse(resp)
defer providerUtils.ReleaseStreamingResponse(ctx, resp)

if resp.BodyStream() == nil {
bifrostErr := providerUtils.NewBifrostOperationError(
Expand Down Expand Up @@ -739,6 +740,10 @@ func HandleAnthropicChatCompletionStreaming(
}
eventType, eventDataBytes, readErr := sseReader.ReadEvent()
if readErr != nil {
// Recheck context cancellation
if ctx.Err() != nil {
return
}
if readErr != io.EOF {
ctx.SetValue(schemas.BifrostContextKeyStreamEndIndicator, true)
logger.Warn("Error reading %s stream: %v", providerName, readErr)
Expand Down Expand Up @@ -1030,15 +1035,14 @@ func (provider *AnthropicProvider) ResponsesStream(ctx *schemas.BifrostContext,
headers["x-api-key"] = key.Value.GetValue()
}

providerUtils.SetStreamIdleTimeoutIfEmpty(ctx, provider.networkConfig.StreamIdleTimeoutInSeconds)

return HandleAnthropicResponsesStream(
ctx,
provider.streamingClient,
provider.buildRequestURL(ctx, "/v1/messages", schemas.ResponsesStreamRequest),
jsonBody,
headers,
provider.networkConfig.ExtraHeaders,
provider.networkConfig.StreamIdleTimeoutInSeconds,
provider.networkConfig.BetaHeaderOverrides,
providerUtils.ShouldSendBackRawRequest(ctx, provider.sendBackRawRequest),
providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse),
Expand All @@ -1059,6 +1063,7 @@ func HandleAnthropicResponsesStream(
jsonBody []byte,
headers map[string]string,
extraHeaders map[string]string,
streamIdleTimeoutInSeconds int,
betaHeaderOverrides map[string]bool,
sendBackRawRequest bool,
sendBackRawResponse bool,
Expand All @@ -1068,6 +1073,7 @@ func HandleAnthropicResponsesStream(
logger schemas.Logger,
postHookSpanFinalizer func(context.Context),
) (chan *schemas.BifrostStreamChunk, *schemas.BifrostError) {
providerUtils.SetStreamIdleTimeoutIfEmpty(ctx, streamIdleTimeoutInSeconds)
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
resp.StreamBody = true
Expand Down Expand Up @@ -1103,7 +1109,7 @@ func HandleAnthropicResponsesStream(
providerUtils.DrainLargePayloadRemainder(ctx)
}
if err != nil {
defer providerUtils.ReleaseStreamingResponse(resp)
defer providerUtils.ReleaseStreamingResponse(ctx, resp)
if errors.Is(err, context.Canceled) {
return nil, providerUtils.EnrichError(ctx, &schemas.BifrostError{
IsBifrostError: false,
Expand All @@ -1125,7 +1131,7 @@ func HandleAnthropicResponsesStream(

// Check for HTTP errors
if resp.StatusCode() != fasthttp.StatusOK {
defer providerUtils.ReleaseStreamingResponse(resp)
defer providerUtils.ReleaseStreamingResponse(ctx, resp)
return nil, providerUtils.EnrichError(ctx, parseAnthropicError(resp), jsonBody, nil, sendBackRawRequest, sendBackRawResponse)
}

Expand All @@ -1150,7 +1156,7 @@ func HandleAnthropicResponsesStream(
}
close(responseChan)
}()
defer providerUtils.ReleaseStreamingResponse(resp)
defer providerUtils.ReleaseStreamingResponse(ctx, resp)
// If body stream is nil, return an error
if resp.BodyStream() == nil {
bifrostErr := providerUtils.NewBifrostOperationError(
Expand Down Expand Up @@ -1204,6 +1210,10 @@ func HandleAnthropicResponsesStream(
}
eventType, eventDataBytes, readErr := sseReader.ReadEvent()
if readErr != nil {
// Recheck context cancellation
if ctx.Err() != nil {
return
}
if readErr != io.EOF {
ctx.SetValue(schemas.BifrostContextKeyStreamEndIndicator, true)
logger.Warn("Error reading %s stream: %v", providerName, readErr)
Expand Down Expand Up @@ -2647,7 +2657,7 @@ func (provider *AnthropicProvider) PassthroughStream(

activeClient := providerUtils.PrepareResponseStreaming(ctx, provider.streamingClient, resp)
if err := activeClient.Do(fasthttpReq, resp); err != nil {
providerUtils.ReleaseStreamingResponse(resp)
providerUtils.ReleaseStreamingResponse(ctx, resp)
if errors.Is(err, context.Canceled) {
return nil, &schemas.BifrostError{
IsBifrostError: false,
Expand All @@ -2669,7 +2679,7 @@ func (provider *AnthropicProvider) PassthroughStream(

bodyStream := resp.BodyStream()
if bodyStream == nil {
providerUtils.ReleaseStreamingResponse(resp)
providerUtils.ReleaseStreamingResponse(ctx, resp)
return nil, providerUtils.NewBifrostOperationError(
"provider returned an empty stream body",
fmt.Errorf("provider returned an empty stream body"),
Expand Down Expand Up @@ -2700,7 +2710,7 @@ func (provider *AnthropicProvider) PassthroughStream(
}
close(ch)
}()
defer providerUtils.ReleaseStreamingResponse(resp)
defer providerUtils.ReleaseStreamingResponse(ctx, resp)
defer stopIdleTimeout()
defer stopCancellation()

Expand Down
14 changes: 14 additions & 0 deletions core/providers/anthropic/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,20 @@ func ToAnthropicChatRequest(ctx *schemas.BifrostContext, bifrostReq *schemas.Bif
anthropicReq.Messages = anthropicMessages
anthropicReq.System = systemContent

// Trim trailing whitespace from the last assistant message text blocks
// ContentStr is converted to a single text ContentBlock during message conversion
// so we trim the text of that block instead.
lastMsgIndex := len(anthropicReq.Messages) - 1
if lastMsgIndex >= 0 && anthropicReq.Messages[lastMsgIndex].Role == AnthropicMessageRoleAssistant {
blocks := anthropicReq.Messages[lastMsgIndex].Content.ContentBlocks
for j := len(blocks) - 1; j >= 0; j-- {
if blocks[j].Type == AnthropicContentBlockTypeText && blocks[j].Text != nil {
anthropicReq.Messages[lastMsgIndex].Content.ContentBlocks[j].Text = schemas.Ptr(strings.TrimRight(*blocks[j].Text, " \n\r\t"))
break
}
}
}

// Strip request- and tool-level fields the target Anthropic-family
// provider does not support. Fail-closed tool validation stays in
// ValidateToolsForProvider; this is strip-silently for additive fields.
Expand Down
19 changes: 16 additions & 3 deletions core/providers/anthropic/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -2738,9 +2738,8 @@ func (response *AnthropicMessageResponse) ToBifrostResponsesResponse(ctx *schema

bifrostResp.Model = response.Model

// Preserve stop reason from Anthropic response
if response.StopReason != "" {
bifrostResp.StopReason = schemas.Ptr(string(response.StopReason))
bifrostResp.StopReason = schemas.Ptr(ConvertAnthropicFinishReasonToBifrost(response.StopReason))
}

return bifrostResp
Expand Down Expand Up @@ -3344,6 +3343,20 @@ func ConvertBifrostMessagesToAnthropicMessages(ctx *schemas.BifrostContext, bifr
// Flush any remaining pending tool calls (with tracking)
flushPendingToolCallsWithTracking()

// Trim trailing whitespace from the last assistant message
// ContentStr is converted to a single text ContentBlock during message conversion
// so we trim the text of that block instead.
lastMsgIndex := len(anthropicMessages) - 1
if isRequestMessage && lastMsgIndex >= 0 && anthropicMessages[lastMsgIndex].Role == AnthropicMessageRoleAssistant {
blocks := anthropicMessages[lastMsgIndex].Content.ContentBlocks
for j := len(blocks) - 1; j >= 0; j-- {
if blocks[j].Type == AnthropicContentBlockTypeText && blocks[j].Text != nil {
anthropicMessages[lastMsgIndex].Content.ContentBlocks[j].Text = schemas.Ptr(strings.TrimRight(*blocks[j].Text, " \n\r\t"))
break
}
}
}

return anthropicMessages, systemContent
}

Expand Down Expand Up @@ -5957,4 +5970,4 @@ func generateSyntheticInputJSONDeltas(argumentsJSON string, contentIndex *int) [
}

return events
}
}
22 changes: 12 additions & 10 deletions core/providers/anthropic/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ var ProviderFeatures = map[schemas.ModelProvider]ProviderFeatureSupport{
// WebSearch, CodeExecution, FastMode, TaskBudgets, AdvisorTool,
// InferenceGeo, RedactThinking, AdvancedToolUse (full), PromptCachingScope.
schemas.Bedrock: {
ComputerUse: true, Bash: true, Memory: true, TextEditor: true, ToolSearch: true,
WebSearch: true,
CodeExecution: true,
ComputerUse: true, Bash: true, Memory: true, TextEditor: true, ToolSearch: true,
ContainerBasic: true,
// StructuredOutputs: kept true to match pre-existing behavior and the
// provider_feature_support_test.go assertion, but NEITHER B-header
Expand Down Expand Up @@ -1200,18 +1202,18 @@ const (
type AnthropicToolName string

const (
AnthropicToolNameComputer AnthropicToolName = "computer"
AnthropicToolNameWebSearch AnthropicToolName = "web_search"
AnthropicToolNameWebFetch AnthropicToolName = "web_fetch"
AnthropicToolNameBash AnthropicToolName = "bash"
AnthropicToolNameTextEditor AnthropicToolName = "str_replace_based_edit_tool"
AnthropicToolNameComputer AnthropicToolName = "computer"
AnthropicToolNameWebSearch AnthropicToolName = "web_search"
AnthropicToolNameWebFetch AnthropicToolName = "web_fetch"
AnthropicToolNameBash AnthropicToolName = "bash"
AnthropicToolNameTextEditor AnthropicToolName = "str_replace_based_edit_tool"
// AnthropicToolNameTextEditorLegacy is the name required for text_editor_20250124
// and text_editor_20250429. Newer text_editor_20250728+ use AnthropicToolNameTextEditor.
AnthropicToolNameTextEditorLegacy AnthropicToolName = "str_replace_editor"
AnthropicToolNameCodeExecution AnthropicToolName = "code_execution"
AnthropicToolNameMemory AnthropicToolName = "memory"
AnthropicToolNameToolSearchBM25 AnthropicToolName = "tool_search_tool_bm25"
AnthropicToolNameToolSearchRegex AnthropicToolName = "tool_search_tool_regex"
AnthropicToolNameCodeExecution AnthropicToolName = "code_execution"
AnthropicToolNameMemory AnthropicToolName = "memory"
AnthropicToolNameToolSearchBM25 AnthropicToolName = "tool_search_tool_bm25"
AnthropicToolNameToolSearchRegex AnthropicToolName = "tool_search_tool_regex"
)

type AnthropicToolComputerUse struct {
Expand Down
8 changes: 8 additions & 0 deletions core/providers/anthropic/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2654,6 +2654,14 @@ func convertResponsesTextConfigToAnthropicOutputFormat(textConfig *schemas.Respo
schema["required"] = format.JSONSchema.Required
}

if format.JSONSchema.Defs != nil {
schema["$defs"] = *format.JSONSchema.Defs
}

if format.JSONSchema.Definitions != nil {
schema["definitions"] = *format.JSONSchema.Definitions
}

if format.JSONSchema.Type != nil && *format.JSONSchema.Type == "object" {
schema["additionalProperties"] = false
} else if format.JSONSchema.AdditionalProperties != nil {
Expand Down
Loading