package claudemessages import ( "fmt" "strings" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/dto" "github.com/QuantumNous/new-api/relay/reasonmap" sharedclaude "github.com/QuantumNous/new-api/service/relayconvert/internal/shared/claude" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) type ClaudeResponseInfo struct { ResponseId string Created int64 Model string ResponseText strings.Builder Usage *dto.Usage Done bool } func StopReasonClaudeToOpenAI(reason string) string { return reasonmap.ClaudeStopReasonToOpenAIFinishReason(reason) } func StreamResponseClaude2OpenAI(claudeResponse *dto.ClaudeResponse) *dto.ChatCompletionsStreamResponse { var response dto.ChatCompletionsStreamResponse response.Object = "chat.completion.chunk" response.Model = claudeResponse.Model response.Choices = make([]dto.ChatCompletionsStreamResponseChoice, 0) tools := make([]dto.ToolCallResponse, 0) fcIdx := 0 if claudeResponse.Index != nil { fcIdx = *claudeResponse.Index } var choice dto.ChatCompletionsStreamResponseChoice if claudeResponse.Type == "message_start" { if claudeResponse.Message != nil { response.Id = claudeResponse.Message.Id response.Model = claudeResponse.Message.Model } choice.Delta.SetContentString("") choice.Delta.Role = "assistant" } else if claudeResponse.Type == "content_block_start" { if claudeResponse.ContentBlock != nil { if claudeResponse.ContentBlock.Type == "text" && claudeResponse.ContentBlock.Text != nil { choice.Delta.SetContentString(*claudeResponse.ContentBlock.Text) } if claudeResponse.ContentBlock.Type == "tool_use" { tools = append(tools, dto.ToolCallResponse{ Index: common.GetPointer(fcIdx), ID: claudeResponse.ContentBlock.Id, Type: "function", Function: dto.FunctionResponse{ Name: claudeResponse.ContentBlock.Name, Arguments: "", }, }) } } else { return nil } } else if claudeResponse.Type == "content_block_delta" { if claudeResponse.Delta != nil { choice.Delta.Content = claudeResponse.Delta.Text switch claudeResponse.Delta.Type { case "input_json_delta": tools = append(tools, dto.ToolCallResponse{ Type: "function", Index: common.GetPointer(fcIdx), Function: dto.FunctionResponse{ Arguments: *claudeResponse.Delta.PartialJson, }, }) case "signature_delta": signatureContent := "\n" choice.Delta.ReasoningContent = &signatureContent case "thinking_delta": choice.Delta.ReasoningContent = claudeResponse.Delta.Thinking } } } else if claudeResponse.Type == "message_delta" { if claudeResponse.Delta != nil && claudeResponse.Delta.StopReason != nil { finishReason := StopReasonClaudeToOpenAI(*claudeResponse.Delta.StopReason) if finishReason != "null" { choice.FinishReason = &finishReason } } } else if claudeResponse.Type == "message_stop" { return nil } else { return nil } if len(tools) > 0 { choice.Delta.Content = nil choice.Delta.ToolCalls = tools } response.Choices = append(response.Choices, choice) return &response } func ResponseClaude2OpenAI(claudeResponse *dto.ClaudeResponse) *dto.OpenAITextResponse { choices := make([]dto.OpenAITextResponseChoice, 0) fullTextResponse := dto.OpenAITextResponse{ Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()), Object: "chat.completion", Created: common.GetTimestamp(), } var responseText string var responseThinking string if len(claudeResponse.Content) > 0 { responseText = claudeResponse.Content[0].GetText() if claudeResponse.Content[0].Thinking != nil { responseThinking = *claudeResponse.Content[0].Thinking } } tools := make([]dto.ToolCallResponse, 0) thinkingContent := "" fullTextResponse.Id = claudeResponse.Id for _, message := range claudeResponse.Content { switch message.Type { case "tool_use": args, _ := common.Marshal(message.Input) tools = append(tools, dto.ToolCallResponse{ ID: message.Id, Type: "function", Function: dto.FunctionResponse{ Name: message.Name, Arguments: string(args), }, }) case "thinking": if message.Thinking != nil { thinkingContent = *message.Thinking } case "text": responseText = message.GetText() } } choice := dto.OpenAITextResponseChoice{ Index: 0, Message: dto.Message{ Role: "assistant", }, FinishReason: StopReasonClaudeToOpenAI(claudeResponse.StopReason), } choice.SetStringContent(responseText) if len(responseThinking) > 0 { choice.ReasoningContent = &responseThinking } if len(tools) > 0 { choice.Message.SetToolCalls(tools) } if thinkingContent != "" { choice.Message.ReasoningContent = &thinkingContent } fullTextResponse.Model = claudeResponse.Model choices = append(choices, choice) fullTextResponse.Choices = choices return &fullTextResponse } func UsageFromClaudeAPIUsage(usage *dto.ClaudeUsage) *dto.Usage { if usage == nil { return &dto.Usage{} } semanticUsage := &dto.Usage{ PromptTokens: usage.InputTokens, CompletionTokens: usage.OutputTokens, UsageSemantic: "anthropic", UsageSource: "anthropic", BillingUsage: dto.CloneBillingUsage(usage.BillingUsage), } if semanticUsage.BillingUsage == nil { semanticUsage.BillingUsage = dto.NewClaudeMessagesBillingUsage(usage) } semanticUsage.PromptTokensDetails.CachedTokens = usage.CacheReadInputTokens semanticUsage.PromptTokensDetails.CachedCreationTokens = usage.CacheCreationInputTokens semanticUsage.ClaudeCacheCreation5mTokens = usage.GetCacheCreation5mTokens() semanticUsage.ClaudeCacheCreation1hTokens = usage.GetCacheCreation1hTokens() return UsageFromClaudeUsage(semanticUsage) } func UsageFromClaudeUsage(usage *dto.Usage) *dto.Usage { mapped := buildOpenAIStyleUsageFromClaudeUsage(usage) return &mapped } func cacheCreationTokensForOpenAIUsage(usage *dto.Usage) int { if usage == nil { return 0 } splitCacheCreationTokens := usage.ClaudeCacheCreation5mTokens + usage.ClaudeCacheCreation1hTokens if splitCacheCreationTokens == 0 { return usage.PromptTokensDetails.CachedCreationTokens } if usage.PromptTokensDetails.CachedCreationTokens > splitCacheCreationTokens { return usage.PromptTokensDetails.CachedCreationTokens } return splitCacheCreationTokens } func buildOpenAIStyleUsageFromClaudeUsage(usage *dto.Usage) dto.Usage { if usage == nil { return dto.Usage{} } clone := *usage clone.BillingUsage = dto.CloneBillingUsage(usage.BillingUsage) clone.ClaudeCacheCreation5mTokens, clone.ClaudeCacheCreation1hTokens = sharedclaude.NormalizeCacheCreationSplit( usage.PromptTokensDetails.CachedCreationTokens, usage.ClaudeCacheCreation5mTokens, usage.ClaudeCacheCreation1hTokens, ) cacheCreationTokens := cacheCreationTokensForOpenAIUsage(usage) totalInputTokens := usage.PromptTokens + usage.PromptTokensDetails.CachedTokens + cacheCreationTokens clone.PromptTokens = totalInputTokens clone.InputTokens = totalInputTokens clone.TotalTokens = totalInputTokens + usage.CompletionTokens clone.UsageSemantic = "openai" clone.UsageSource = "anthropic" return clone } func BuildMessageDeltaPatchUsage(claudeResponse *dto.ClaudeResponse, claudeInfo *ClaudeResponseInfo) *dto.ClaudeUsage { usage := &dto.ClaudeUsage{} if claudeResponse != nil && claudeResponse.Usage != nil { *usage = *claudeResponse.Usage } if claudeInfo == nil || claudeInfo.Usage == nil { return usage } if usage.InputTokens == 0 && claudeInfo.Usage.PromptTokens > 0 { usage.InputTokens = claudeInfo.Usage.PromptTokens } if usage.CacheReadInputTokens == 0 && claudeInfo.Usage.PromptTokensDetails.CachedTokens > 0 { usage.CacheReadInputTokens = claudeInfo.Usage.PromptTokensDetails.CachedTokens } if usage.CacheCreationInputTokens == 0 && claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens > 0 { usage.CacheCreationInputTokens = claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens } cacheCreation5m := 0 cacheCreation1h := 0 if usage.CacheCreation != nil { cacheCreation5m = usage.CacheCreation.Ephemeral5mInputTokens cacheCreation1h = usage.CacheCreation.Ephemeral1hInputTokens } else { cacheCreation5m = claudeInfo.Usage.ClaudeCacheCreation5mTokens cacheCreation1h = claudeInfo.Usage.ClaudeCacheCreation1hTokens } cacheCreation5m, cacheCreation1h = sharedclaude.NormalizeCacheCreationSplit( usage.CacheCreationInputTokens, cacheCreation5m, cacheCreation1h, ) if usage.CacheCreation == nil && (cacheCreation5m > 0 || cacheCreation1h > 0) { usage.CacheCreation = &dto.ClaudeCacheCreationUsage{} } if usage.CacheCreation != nil { usage.CacheCreation.Ephemeral5mInputTokens = cacheCreation5m usage.CacheCreation.Ephemeral1hInputTokens = cacheCreation1h } return usage } func claudeBillingUsageFromSemanticUsage(usage *dto.Usage) *dto.BillingUsage { if usage == nil { return nil } cacheCreation5m, cacheCreation1h := sharedclaude.NormalizeCacheCreationSplit( usage.PromptTokensDetails.CachedCreationTokens, usage.ClaudeCacheCreation5mTokens, usage.ClaudeCacheCreation1hTokens, ) claudeUsage := &dto.ClaudeUsage{ InputTokens: usage.PromptTokens, CacheCreationInputTokens: usage.PromptTokensDetails.CachedCreationTokens, CacheReadInputTokens: usage.PromptTokensDetails.CachedTokens, OutputTokens: usage.CompletionTokens, } if cacheCreation5m > 0 || cacheCreation1h > 0 { claudeUsage.CacheCreation = &dto.ClaudeCacheCreationUsage{ Ephemeral5mInputTokens: cacheCreation5m, Ephemeral1hInputTokens: cacheCreation1h, } } return dto.NewClaudeMessagesBillingUsage(claudeUsage) } func PatchClaudeMessageDeltaUsageData(data string, usage *dto.ClaudeUsage) string { if data == "" || usage == nil { return data } data = setMessageDeltaUsageInt(data, "usage.input_tokens", usage.InputTokens) data = setMessageDeltaUsageInt(data, "usage.cache_read_input_tokens", usage.CacheReadInputTokens) data = setMessageDeltaUsageInt(data, "usage.cache_creation_input_tokens", usage.CacheCreationInputTokens) if usage.CacheCreation != nil { data = setMessageDeltaUsageInt(data, "usage.cache_creation.ephemeral_5m_input_tokens", usage.CacheCreation.Ephemeral5mInputTokens) data = setMessageDeltaUsageInt(data, "usage.cache_creation.ephemeral_1h_input_tokens", usage.CacheCreation.Ephemeral1hInputTokens) } return data } func setMessageDeltaUsageInt(data string, path string, localValue int) string { if localValue <= 0 { return data } upstreamValue := gjson.Get(data, path) if upstreamValue.Exists() && upstreamValue.Int() > 0 { return data } patchedData, err := sjson.Set(data, path, localValue) if err != nil { return data } return patchedData } func FormatClaudeResponseInfo(claudeResponse *dto.ClaudeResponse, oaiResponse *dto.ChatCompletionsStreamResponse, claudeInfo *ClaudeResponseInfo) bool { if claudeInfo == nil { return false } if claudeInfo.Usage == nil { claudeInfo.Usage = &dto.Usage{} } if claudeResponse.Type == "message_start" { if claudeResponse.Message != nil { claudeInfo.ResponseId = claudeResponse.Message.Id claudeInfo.Model = claudeResponse.Message.Model } if claudeResponse.Message != nil && claudeResponse.Message.Usage != nil { claudeInfo.Usage.PromptTokens = claudeResponse.Message.Usage.InputTokens claudeInfo.Usage.UsageSemantic = "anthropic" claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Message.Usage.CacheReadInputTokens claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Message.Usage.CacheCreationInputTokens claudeInfo.Usage.ClaudeCacheCreation5mTokens = claudeResponse.Message.Usage.GetCacheCreation5mTokens() claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Message.Usage.GetCacheCreation1hTokens() claudeInfo.Usage.CompletionTokens = claudeResponse.Message.Usage.OutputTokens claudeInfo.Usage.BillingUsage = claudeBillingUsageFromSemanticUsage(claudeInfo.Usage) } } else if claudeResponse.Type == "content_block_delta" { if claudeResponse.Delta != nil { if claudeResponse.Delta.Text != nil { claudeInfo.ResponseText.WriteString(*claudeResponse.Delta.Text) } if claudeResponse.Delta.Thinking != nil { claudeInfo.ResponseText.WriteString(*claudeResponse.Delta.Thinking) } } } else if claudeResponse.Type == "message_delta" { if claudeResponse.Usage != nil { claudeInfo.Usage.UsageSemantic = "anthropic" if claudeResponse.Usage.InputTokens > 0 { claudeInfo.Usage.PromptTokens = claudeResponse.Usage.InputTokens } if claudeResponse.Usage.CacheReadInputTokens > 0 { claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens } if claudeResponse.Usage.CacheCreationInputTokens > 0 { claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens } if cacheCreation5m := claudeResponse.Usage.GetCacheCreation5mTokens(); cacheCreation5m > 0 { claudeInfo.Usage.ClaudeCacheCreation5mTokens = cacheCreation5m } if cacheCreation1h := claudeResponse.Usage.GetCacheCreation1hTokens(); cacheCreation1h > 0 { claudeInfo.Usage.ClaudeCacheCreation1hTokens = cacheCreation1h } if claudeResponse.Usage.OutputTokens > 0 { claudeInfo.Usage.CompletionTokens = claudeResponse.Usage.OutputTokens } claudeInfo.Usage.TotalTokens = claudeInfo.Usage.PromptTokens + claudeInfo.Usage.CompletionTokens claudeInfo.Usage.BillingUsage = claudeBillingUsageFromSemanticUsage(claudeInfo.Usage) } claudeInfo.Done = true } else if claudeResponse.Type == "content_block_start" { } else { return false } if oaiResponse != nil { oaiResponse.Id = claudeInfo.ResponseId oaiResponse.Created = claudeInfo.Created oaiResponse.Model = claudeInfo.Model } return true }