Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
241 lines
7.6 KiB
Go
241 lines
7.6 KiB
Go
package claudemessages
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/QuantumNous/new-api/relaykit/dto"
|
|
"github.com/QuantumNous/new-api/relaykit/relayconvert/convmeta"
|
|
kitutil "github.com/QuantumNous/new-api/relaykit/relayconvert/kitutil"
|
|
"github.com/QuantumNous/new-api/relaykit/relayconvert/reasoning"
|
|
)
|
|
|
|
const (
|
|
webSearchMaxUsesLow = 1
|
|
webSearchMaxUsesMedium = 5
|
|
webSearchMaxUsesHigh = 10
|
|
)
|
|
|
|
type openRouterRequestReasoning struct {
|
|
Enabled *bool `json:"enabled,omitempty"`
|
|
Effort string `json:"effort,omitempty"`
|
|
MaxTokens int `json:"max_tokens,omitempty"`
|
|
Exclude bool `json:"exclude,omitempty"`
|
|
}
|
|
|
|
func ClaudeMessagesRequestToOpenAIChat(ctx context.Context, claudeRequest dto.ClaudeRequest, info convmeta.Meta) (*dto.GeneralOpenAIRequest, error) {
|
|
openAIRequest := dto.GeneralOpenAIRequest{
|
|
Model: claudeRequest.Model,
|
|
Temperature: claudeRequest.Temperature,
|
|
}
|
|
if claudeRequest.MaxTokens != nil {
|
|
openAIRequest.MaxTokens = kitutil.GetPointer(*claudeRequest.MaxTokens)
|
|
}
|
|
if claudeRequest.TopP != nil {
|
|
openAIRequest.TopP = kitutil.GetPointer(*claudeRequest.TopP)
|
|
}
|
|
if claudeRequest.TopK != nil {
|
|
openAIRequest.TopK = kitutil.GetPointer(*claudeRequest.TopK)
|
|
}
|
|
if claudeRequest.Stream != nil {
|
|
openAIRequest.Stream = kitutil.GetPointer(*claudeRequest.Stream)
|
|
}
|
|
reasoningIntent, effectiveEffort, err := claudeRequestReasoningIntent(ctx, &claudeRequest, info)
|
|
if err != nil {
|
|
return nil, reasoning.AsClientError(err)
|
|
}
|
|
|
|
isOpenRouter := convmeta.OptionsOf(info).OpenRouterDialect
|
|
if isOpenRouter {
|
|
if effort := claudeRequest.GetEfforts(); effort != "" {
|
|
effortBytes, _ := kitutil.Marshal(effort)
|
|
openAIRequest.Verbosity = effortBytes
|
|
}
|
|
if !reasoningIntent.IsEmpty() {
|
|
var reasoningConfig openRouterRequestReasoning
|
|
disabled := reasoningIntent.Mode == reasoning.ModeDisabled || reasoningIntent.Effort == reasoning.EffortNone
|
|
enabled := !disabled
|
|
reasoningConfig.Enabled = &enabled
|
|
if enabled && reasoningIntent.BudgetTokens != nil && reasoningIntent.Mode != reasoning.ModeAdaptive {
|
|
reasoningConfig = openRouterRequestReasoning{
|
|
Enabled: &enabled,
|
|
MaxTokens: *reasoningIntent.BudgetTokens,
|
|
}
|
|
} else if enabled {
|
|
reasoningConfig.Effort = string(reasoning.EffectiveEffort(reasoningIntent))
|
|
}
|
|
if reasoningIntent.IncludeThoughts != nil {
|
|
reasoningConfig.Exclude = !*reasoningIntent.IncludeThoughts
|
|
}
|
|
reasoningJSON, err := kitutil.Marshal(reasoningConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal reasoning: %w", err)
|
|
}
|
|
openAIRequest.Reasoning = reasoningJSON
|
|
}
|
|
} else {
|
|
if err := reasoning.ApplyToOpenAIChat(&openAIRequest, reasoningIntent); err != nil {
|
|
return nil, reasoning.AsClientError(err)
|
|
}
|
|
if info != nil {
|
|
// Keep the outgoing -thinking suffix so a cascaded downstream
|
|
// new-api can recover reasoning intent from the model name. This
|
|
// is an emission-side policy, not converter-side suffix parsing.
|
|
thinkingSuffix := "-thinking"
|
|
if strings.HasSuffix(info.GetOriginModelName(), thinkingSuffix) &&
|
|
!strings.HasSuffix(openAIRequest.Model, thinkingSuffix) {
|
|
openAIRequest.Model = openAIRequest.Model + thinkingSuffix
|
|
}
|
|
}
|
|
}
|
|
if info != nil && effectiveEffort != "" {
|
|
info.SetReasoningEffort(string(effectiveEffort))
|
|
}
|
|
|
|
if len(claudeRequest.StopSequences) == 1 {
|
|
openAIRequest.Stop = claudeRequest.StopSequences[0]
|
|
} else if len(claudeRequest.StopSequences) > 1 {
|
|
openAIRequest.Stop = claudeRequest.StopSequences
|
|
}
|
|
|
|
tools, _ := kitutil.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(convmeta.UpstreamModelName(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 {
|
|
var systemStr strings.Builder
|
|
for _, system := range systems {
|
|
if system.Text != nil {
|
|
systemStr.WriteString(*system.Text)
|
|
}
|
|
}
|
|
openAIMessage.SetStringContent(systemStr.String())
|
|
}
|
|
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, _ := kitutil.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 any) string {
|
|
b, err := kitutil.Marshal(v)
|
|
if err != nil {
|
|
return "{}"
|
|
}
|
|
return string(b)
|
|
}
|