package claudemessages import ( "fmt" "strings" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/dto" relaycommon "github.com/QuantumNous/new-api/relay/common" relaymeta "github.com/QuantumNous/new-api/service/relayconvert/internal/meta" ) const ( webSearchMaxUsesLow = 1 webSearchMaxUsesMedium = 5 webSearchMaxUsesHigh = 10 ) type openRouterRequestReasoning struct { Enabled bool `json:"enabled"` Effort string `json:"effort,omitempty"` MaxTokens int `json:"max_tokens,omitempty"` Exclude bool `json:"exclude,omitempty"` } func ClaudeMessagesRequestToOpenAIChat(claudeRequest dto.ClaudeRequest, info *relaycommon.RelayInfo) (*dto.GeneralOpenAIRequest, error) { openAIRequest := dto.GeneralOpenAIRequest{ Model: claudeRequest.Model, Temperature: claudeRequest.Temperature, } if claudeRequest.MaxTokens != nil { openAIRequest.MaxTokens = common.GetPointer(*claudeRequest.MaxTokens) } if claudeRequest.TopP != nil { openAIRequest.TopP = common.GetPointer(*claudeRequest.TopP) } if claudeRequest.TopK != nil { openAIRequest.TopK = common.GetPointer(*claudeRequest.TopK) } if claudeRequest.Stream != nil { openAIRequest.Stream = common.GetPointer(*claudeRequest.Stream) } isOpenRouter := relaymeta.RelayInfoChannelType(info) == constant.ChannelTypeOpenRouter if isOpenRouter { if effort := claudeRequest.GetEfforts(); effort != "" { effortBytes, _ := common.Marshal(effort) openAIRequest.Verbosity = effortBytes } if claudeRequest.Thinking != nil { var reasoningConfig openRouterRequestReasoning if claudeRequest.Thinking.Type == "enabled" { reasoningConfig = openRouterRequestReasoning{ Enabled: true, MaxTokens: claudeRequest.Thinking.GetBudgetTokens(), } } else if claudeRequest.Thinking.Type == "adaptive" { reasoningConfig = openRouterRequestReasoning{ Enabled: true, } } reasoningJSON, err := common.Marshal(reasoningConfig) if err != nil { return nil, fmt.Errorf("failed to marshal reasoning: %w", err) } openAIRequest.Reasoning = reasoningJSON } } else if info != nil { thinkingSuffix := "-thinking" if strings.HasSuffix(info.OriginModelName, thinkingSuffix) && !strings.HasSuffix(openAIRequest.Model, thinkingSuffix) { openAIRequest.Model = openAIRequest.Model + thinkingSuffix } } if len(claudeRequest.StopSequences) == 1 { openAIRequest.Stop = claudeRequest.StopSequences[0] } else if len(claudeRequest.StopSequences) > 1 { openAIRequest.Stop = claudeRequest.StopSequences } tools, _ := common.Any2Type[[]dto.Tool](claudeRequest.Tools) openAITools := make([]dto.ToolCallRequest, 0) for _, claudeTool := range tools { openAITool := dto.ToolCallRequest{ Type: "function", Function: dto.FunctionRequest{ Name: claudeTool.Name, Description: claudeTool.Description, Parameters: claudeTool.InputSchema, }, } openAITools = append(openAITools, openAITool) } openAIRequest.Tools = openAITools openAIMessages := make([]dto.Message, 0) if claudeRequest.System != nil { if claudeRequest.IsStringSystem() && claudeRequest.GetStringSystem() != "" { openAIMessage := dto.Message{ Role: "system", } openAIMessage.SetStringContent(claudeRequest.GetStringSystem()) openAIMessages = append(openAIMessages, openAIMessage) } else { systems := claudeRequest.ParseSystem() if len(systems) > 0 { openAIMessage := dto.Message{ Role: "system", } isOpenRouterClaude := isOpenRouter && strings.HasPrefix(relaymeta.RelayInfoUpstreamModelName(info), "anthropic/claude") if isOpenRouterClaude { systemMediaMessages := make([]dto.MediaContent, 0, len(systems)) for _, system := range systems { message := dto.MediaContent{ Type: "text", Text: system.GetText(), CacheControl: system.CacheControl, } systemMediaMessages = append(systemMediaMessages, message) } openAIMessage.SetMediaContent(systemMediaMessages) } else { systemStr := "" for _, system := range systems { if system.Text != nil { systemStr += *system.Text } } openAIMessage.SetStringContent(systemStr) } openAIMessages = append(openAIMessages, openAIMessage) } } } for _, claudeMessage := range claudeRequest.Messages { openAIMessage := dto.Message{ Role: claudeMessage.Role, } if claudeMessage.IsStringContent() { openAIMessage.SetStringContent(claudeMessage.GetStringContent()) } else { content, err := claudeMessage.ParseContent() if err != nil { return nil, err } var toolCalls []dto.ToolCallRequest mediaMessages := make([]dto.MediaContent, 0, len(content)) for _, mediaMsg := range content { switch mediaMsg.Type { case "text", "input_text": message := dto.MediaContent{ Type: "text", Text: mediaMsg.GetText(), CacheControl: mediaMsg.CacheControl, } mediaMessages = append(mediaMessages, message) case "image": imageData := fmt.Sprintf("data:%s;base64,%s", mediaMsg.Source.MediaType, mediaMsg.Source.Data) mediaMessage := dto.MediaContent{ Type: "image_url", ImageUrl: &dto.MessageImageUrl{Url: imageData}, } mediaMessages = append(mediaMessages, mediaMessage) case "tool_use": toolCall := dto.ToolCallRequest{ ID: mediaMsg.Id, Type: "function", Function: dto.FunctionRequest{ Name: mediaMsg.Name, Arguments: requestToJSONString(mediaMsg.Input), }, } toolCalls = append(toolCalls, toolCall) case "tool_result": toolName := mediaMsg.Name if toolName == "" { toolName = claudeRequest.SearchToolNameByToolCallId(mediaMsg.ToolUseId) } oaiToolMessage := dto.Message{ Role: "tool", Name: &toolName, ToolCallId: mediaMsg.ToolUseId, } if mediaMsg.IsStringContent() { oaiToolMessage.SetStringContent(mediaMsg.GetStringContent()) } else { mediaContents := mediaMsg.ParseMediaContent() encodedJSON, _ := common.Marshal(mediaContents) oaiToolMessage.SetStringContent(string(encodedJSON)) } openAIMessages = append(openAIMessages, oaiToolMessage) } } if len(toolCalls) > 0 { openAIMessage.SetToolCalls(toolCalls) } if len(mediaMessages) > 0 && len(toolCalls) == 0 { openAIMessage.SetMediaContent(mediaMessages) } } if len(openAIMessage.ParseContent()) > 0 || len(openAIMessage.ToolCalls) > 0 { openAIMessages = append(openAIMessages, openAIMessage) } } openAIRequest.Messages = openAIMessages return &openAIRequest, nil } func requestToJSONString(v interface{}) string { b, err := common.Marshal(v) if err != nil { return "{}" } return string(b) }