diff --git a/core/providers/gemini/chat.go b/core/providers/gemini/chat.go index 5529b3bc7..9c477c4ff 100644 --- a/core/providers/gemini/chat.go +++ b/core/providers/gemini/chat.go @@ -118,15 +118,6 @@ func (response *GenerateContentResponse) ToBifrostChatResponse() *schemas.Bifros Function: function, } - if part.ThoughtSignature != nil { - thoughtSig := base64.StdEncoding.EncodeToString(part.ThoughtSignature) - toolCall.ExtraContent = map[string]interface{}{ - "google": map[string]interface{}{ - "thought_signature": thoughtSig, - }, - } - } - toolCalls = append(toolCalls, toolCall) } @@ -144,11 +135,22 @@ func (response *GenerateContentResponse) ToBifrostChatResponse() *schemas.Bifros } if part.ThoughtSignature != nil { thoughtSig := base64.StdEncoding.EncodeToString(part.ThoughtSignature) - reasoningDetails = append(reasoningDetails, schemas.ChatReasoningDetails{ + reasoningDetail := schemas.ChatReasoningDetails{ Index: len(reasoningDetails), Type: schemas.BifrostReasoningDetailsTypeEncrypted, Signature: &thoughtSig, - }) + } + + // check if part is tool call + if part.FunctionCall != nil { + callID := part.FunctionCall.Name + if part.FunctionCall.ID != "" { + callID = part.FunctionCall.ID + } + reasoningDetail.ID = schemas.Ptr(fmt.Sprintf("tool_call_%s", callID)) + } + + reasoningDetails = append(reasoningDetails, reasoningDetail) } } diff --git a/core/providers/gemini/utils.go b/core/providers/gemini/utils.go index 166fea03a..371f76dde 100644 --- a/core/providers/gemini/utils.go +++ b/core/providers/gemini/utils.go @@ -2,6 +2,7 @@ package gemini import ( "encoding/base64" + "fmt" "strings" "github.com/bytedance/sonic" @@ -607,15 +608,19 @@ func convertBifrostMessagesToGemini(messages []schemas.ChatMessage) []Content { }, } - // Preserve thought signature from extra_content (required for Gemini 3 Pro) - if toolCall.ExtraContent != nil { - if googleData, ok := toolCall.ExtraContent["google"].(map[string]interface{}); ok { - if thoughtSig, ok := googleData["thought_signature"].(string); ok { + // check in reasoning details array for thought signature with id tool_call_ + if len(message.ChatAssistantMessage.ReasoningDetails) > 0 { + lookupID := fmt.Sprintf("tool_call_%s", callID) + for _, reasoningDetail := range message.ChatAssistantMessage.ReasoningDetails { + if reasoningDetail.ID != nil && *reasoningDetail.ID == lookupID && + reasoningDetail.Type == schemas.BifrostReasoningDetailsTypeEncrypted && + reasoningDetail.Signature != nil { // Decode the base64 string to raw bytes - decoded, err := base64.StdEncoding.DecodeString(thoughtSig) + decoded, err := base64.StdEncoding.DecodeString(*reasoningDetail.Signature) if err == nil { part.ThoughtSignature = decoded } + break } } } diff --git a/core/schemas/chatcompletions.go b/core/schemas/chatcompletions.go index d795e2d4b..090a9664a 100644 --- a/core/schemas/chatcompletions.go +++ b/core/schemas/chatcompletions.go @@ -717,11 +717,10 @@ type ChatAssistantMessageAnnotationCitation struct { // ChatAssistantMessageToolCall represents a tool call in a message type ChatAssistantMessageToolCall struct { - Index uint16 `json:"index"` - Type *string `json:"type,omitempty"` - ID *string `json:"id,omitempty"` - Function ChatAssistantMessageToolCallFunction `json:"function"` - ExtraContent map[string]interface{} `json:"extra_content,omitempty"` // Provider-specific fields (e.g., thought_signature for Gemini) + Index uint16 `json:"index"` + Type *string `json:"type,omitempty"` + ID *string `json:"id,omitempty"` + Function ChatAssistantMessageToolCallFunction `json:"function"` } // ChatAssistantMessageToolCallFunction represents a call to a function. @@ -762,6 +761,7 @@ const ( // Not in OpenAI's spec, but needed to support inter provider reasoning capabilities. type ChatReasoningDetails struct { + ID *string `json:"id,omitempty"` Index int `json:"index"` Type BifrostReasoningDetailsType `json:"type"` Summary *string `json:"summary,omitempty"`