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
19 changes: 19 additions & 0 deletions core/providers/anthropic/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -3486,6 +3486,25 @@ func (req *AnthropicMessageRequest) ToBifrostResponsesRequest(ctx *schemas.Bifro
if include, ok := schemas.SafeExtractStringSlice(req.ExtraParams["include"]); ok {
params.Include = include
}
// Lift the mixed server/client tool opt-in onto the typed parameter: the
// Gemini declaration-drop gate reads Params.IncludeServerSideToolInvocations,
// so leaving it in ExtraParams silently drops function tools when a request
// combines them with a server-side tool (issue #5679). Unregistered fields
// are captured as json.RawMessage holding the bare true/false token, so a
// string comparison is enough.
if raw, exists := req.ExtraParams["include_server_side_tool_invocations"]; exists {
switch v := raw.(type) {
case bool:
params.IncludeServerSideToolInvocations = schemas.Ptr(v)
case json.RawMessage:
switch strings.TrimSpace(string(v)) {
case "true":
params.IncludeServerSideToolInvocations = schemas.Ptr(true)
case "false":
params.IncludeServerSideToolInvocations = schemas.Ptr(false)
}
}
}
if req.ServiceTier != nil {
mapped := MapAnthropicRequestServiceTierToBifrost(*req.ServiceTier)
params.ServiceTier = &mapped
Expand Down
31 changes: 31 additions & 0 deletions core/providers/anthropic/responses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,34 @@ func TestToAnthropicResponsesRequest_ContextManagement_UnsupportedProviderExtraP
t.Errorf("expected context_management to be removed from the outgoing request's ExtraParams even though the feature gate is closed")
}
}

// TestAnthropicIngressLiftsServerSideToolOptIn covers issue #5679: the
// include_server_side_tool_invocations opt-in arrives as an unregistered
// Anthropic field (captured into ExtraParams) but the Gemini declaration-drop
// gate reads the typed Params.IncludeServerSideToolInvocations, so the ingress
// conversion must lift it. Without the lift, combining a server-side tool with
// a function tool on /anthropic/v1/messages routed to Gemini silently drops
// the function declarations.
func TestAnthropicIngressLiftsServerSideToolOptIn(t *testing.T) {
body := []byte(`{
"model": "gemini-3-pro",
"max_tokens": 512,
"messages": [{"role": "user", "content": "search and compute"}],
"include_server_side_tool_invocations": true
}`)

var req AnthropicMessageRequest
if err := req.UnmarshalJSON(body); err != nil {
t.Fatalf("unmarshal request: %v", err)
}

bifrostReq := req.ToBifrostResponsesRequest(nil)
if bifrostReq == nil || bifrostReq.Params == nil {
t.Fatal("converted request or params is nil")
}
if bifrostReq.Params.IncludeServerSideToolInvocations == nil ||
!*bifrostReq.Params.IncludeServerSideToolInvocations {
t.Fatalf("include_server_side_tool_invocations not lifted to typed param: %v",
bifrostReq.Params.IncludeServerSideToolInvocations)
}
}