From 198e723b972b3beb5b169d1676f60e5a83f206d7 Mon Sep 17 00:00:00 2001 From: Lich-Mac-Mini Date: Fri, 17 Apr 2026 20:28:32 +0800 Subject: [PATCH] fix: restore Claude file conversion and preserve stream status --- relay/channel/claude/relay-claude.go | 83 +++++++++++++++++++++------- relay/helper/stream_scanner.go | 6 +- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/relay/channel/claude/relay-claude.go b/relay/channel/claude/relay-claude.go index fa823452..a44c81e0 100644 --- a/relay/channel/claude/relay-claude.go +++ b/relay/channel/claude/relay-claude.go @@ -1,11 +1,13 @@ package claude import ( + "encoding/base64" "encoding/json" "fmt" "io" "net/http" "strings" + "unicode/utf8" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/constant" @@ -35,6 +37,21 @@ func stopReasonClaude2OpenAI(reason string) string { return reasonmap.ClaudeStopReasonToOpenAIFinishReason(reason) } +func inferClaudeFileMimeType(file *dto.MessageFile) string { + if file == nil || file.FileName == "" { + return "" + } + dot := strings.LastIndex(file.FileName, ".") + if dot == -1 || dot+1 >= len(file.FileName) { + return "" + } + mimeType := service.GetMimeTypeByExtension(file.FileName[dot+1:]) + if mimeType == "application/octet-stream" { + return "" + } + return mimeType +} + func maybeMarkClaudeRefusal(c *gin.Context, stopReason string) { if c == nil { return @@ -365,31 +382,59 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe text = "..." } claudeMessage.Content = text - } else { - claudeMediaMessages := make([]dto.ClaudeMediaMessage, 0) - for _, mediaMessage := range message.ParseContent() { - switch mediaMessage.Type { - case "text": + } else { + claudeMediaMessages := make([]dto.ClaudeMediaMessage, 0) + for _, mediaMessage := range message.ParseContent() { + switch mediaMessage.Type { + case "text": if mediaMessage.Text != "" { claudeMediaMessages = append(claudeMediaMessages, dto.ClaudeMediaMessage{ Type: "text", Text: common.GetPointer[string](mediaMessage.Text), }) } - default: - source := mediaMessage.ToFileSource() - if source == nil { - continue - } - base64Data, mimeType, err := service.GetBase64Data(c, source, "formatting image for Claude") - if err != nil { - return nil, fmt.Errorf("get file data failed: %s", err.Error()) - } - claudeMediaMessage := dto.ClaudeMediaMessage{ - Source: &dto.ClaudeMessageSource{ - Type: "base64", - }, - } + default: + var source types.FileSource + if mediaMessage.Type == dto.ContentTypeFile { + file := mediaMessage.GetFile() + if file == nil || file.FileData == "" { + continue + } + source = types.NewFileSourceFromData(file.FileData, inferClaudeFileMimeType(file)) + } else { + source = mediaMessage.ToFileSource() + } + if source == nil { + continue + } + base64Data, mimeType, err := service.GetBase64Data(c, source, "formatting image for Claude") + if err != nil { + return nil, fmt.Errorf("get file data failed: %s", err.Error()) + } + if mimeType == "text/plain" { + decoded, err := base64.StdEncoding.DecodeString(base64Data) + if err != nil { + return nil, fmt.Errorf("decode text file data failed: %s", err.Error()) + } + if utf8.Valid(decoded) { + text := string(decoded) + if text != "" { + claudeMediaMessages = append(claudeMediaMessages, dto.ClaudeMediaMessage{ + Type: "text", + Text: common.GetPointer[string](text), + }) + } + } + continue + } + if mimeType == "" || mimeType == "application/octet-stream" { + continue + } + claudeMediaMessage := dto.ClaudeMediaMessage{ + Source: &dto.ClaudeMessageSource{ + Type: "base64", + }, + } if strings.HasPrefix(mimeType, "application/pdf") { claudeMediaMessage.Type = "document" } else { diff --git a/relay/helper/stream_scanner.go b/relay/helper/stream_scanner.go index a9bc5e16..d6ad4e65 100644 --- a/relay/helper/stream_scanner.go +++ b/relay/helper/stream_scanner.go @@ -40,8 +40,10 @@ func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon return } - // 无条件新建 StreamStatus - info.StreamStatus = relaycommon.NewStreamStatus() + // 保留调用方预先注入的 StreamStatus,避免覆盖已有错误/状态。 + if info.StreamStatus == nil { + info.StreamStatus = relaycommon.NewStreamStatus() + } // 确保响应体总是被关闭 defer func() {