Skip to content

Commit 6f9c23a

Browse files
committed
refactor(translator): consolidate Claude content handling logic - Unified logic for text and image content conversion to improve maintainability. - Introduced `convertClaudeContentPart` utility for consistent content transformation. - Replaced redundant string operations with streamlined JSON modifications. - Adjusted validation checks for message content generation.
1 parent 2d5d06c commit 6f9c23a

File tree

1 file changed

+78
-23
lines changed

1 file changed

+78
-23
lines changed

internal/translator/openai/claude/openai_claude_request.go

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package claude
88
import (
99
"bytes"
1010
"encoding/json"
11-
"strings"
1211

1312
"github.com/tidwall/gjson"
1413
"github.com/tidwall/sjson"
@@ -79,7 +78,9 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
7978
if system.IsArray() {
8079
systemResults := system.Array()
8180
for i := 0; i < len(systemResults); i++ {
82-
systemMsgJSON, _ = sjson.SetRaw(systemMsgJSON, "content.-1", systemResults[i].Raw)
81+
if contentItem, ok := convertClaudeContentPart(systemResults[i]); ok {
82+
systemMsgJSON, _ = sjson.SetRaw(systemMsgJSON, "content.-1", contentItem)
83+
}
8384
}
8485
}
8586
}
@@ -94,29 +95,16 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
9495

9596
// Handle content
9697
if contentResult.Exists() && contentResult.IsArray() {
97-
var textParts []string
98+
var contentItems []string
9899
var toolCalls []interface{}
99100

100101
contentResult.ForEach(func(_, part gjson.Result) bool {
101102
partType := part.Get("type").String()
102103

103104
switch partType {
104-
case "text":
105-
textParts = append(textParts, part.Get("text").String())
106-
107-
case "image":
108-
// Convert Anthropic image format to OpenAI format
109-
if source := part.Get("source"); source.Exists() {
110-
sourceType := source.Get("type").String()
111-
if sourceType == "base64" {
112-
mediaType := source.Get("media_type").String()
113-
data := source.Get("data").String()
114-
imageURL := "data:" + mediaType + ";base64," + data
115-
116-
// For now, add as text since OpenAI image handling is complex
117-
// In a real implementation, you'd need to handle this properly
118-
textParts = append(textParts, "[Image: "+imageURL+"]")
119-
}
105+
case "text", "image":
106+
if contentItem, ok := convertClaudeContentPart(part); ok {
107+
contentItems = append(contentItems, contentItem)
120108
}
121109

122110
case "tool_use":
@@ -149,13 +137,17 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
149137
})
150138

151139
// Create main message if there's text content or tool calls
152-
if len(textParts) > 0 || len(toolCalls) > 0 {
140+
if len(contentItems) > 0 || len(toolCalls) > 0 {
153141
msgJSON := `{"role":"","content":""}`
154142
msgJSON, _ = sjson.Set(msgJSON, "role", role)
155143

156144
// Set content
157-
if len(textParts) > 0 {
158-
msgJSON, _ = sjson.Set(msgJSON, "content", strings.Join(textParts, ""))
145+
if len(contentItems) > 0 {
146+
contentArrayJSON := "[]"
147+
for _, contentItem := range contentItems {
148+
contentArrayJSON, _ = sjson.SetRaw(contentArrayJSON, "-1", contentItem)
149+
}
150+
msgJSON, _ = sjson.SetRaw(msgJSON, "content", contentArrayJSON)
159151
} else {
160152
msgJSON, _ = sjson.Set(msgJSON, "content", "")
161153
}
@@ -166,7 +158,20 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
166158
msgJSON, _ = sjson.SetRaw(msgJSON, "tool_calls", string(toolCallsJSON))
167159
}
168160

169-
if gjson.Get(msgJSON, "content").String() != "" || len(toolCalls) != 0 {
161+
contentValue := gjson.Get(msgJSON, "content")
162+
hasContent := false
163+
switch {
164+
case !contentValue.Exists():
165+
hasContent = false
166+
case contentValue.Type == gjson.String:
167+
hasContent = contentValue.String() != ""
168+
case contentValue.IsArray():
169+
hasContent = len(contentValue.Array()) > 0
170+
default:
171+
hasContent = contentValue.Raw != "" && contentValue.Raw != "null"
172+
}
173+
174+
if hasContent || len(toolCalls) != 0 {
170175
messagesJSON, _ = sjson.Set(messagesJSON, "-1", gjson.Parse(msgJSON).Value())
171176
}
172177
}
@@ -237,3 +242,53 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
237242

238243
return []byte(out)
239244
}
245+
246+
func convertClaudeContentPart(part gjson.Result) (string, bool) {
247+
partType := part.Get("type").String()
248+
249+
switch partType {
250+
case "text":
251+
if !part.Get("text").Exists() {
252+
return "", false
253+
}
254+
textContent := `{"type":"text","text":""}`
255+
textContent, _ = sjson.Set(textContent, "text", part.Get("text").String())
256+
return textContent, true
257+
258+
case "image":
259+
var imageURL string
260+
261+
if source := part.Get("source"); source.Exists() {
262+
sourceType := source.Get("type").String()
263+
switch sourceType {
264+
case "base64":
265+
mediaType := source.Get("media_type").String()
266+
if mediaType == "" {
267+
mediaType = "application/octet-stream"
268+
}
269+
data := source.Get("data").String()
270+
if data != "" {
271+
imageURL = "data:" + mediaType + ";base64," + data
272+
}
273+
case "url":
274+
imageURL = source.Get("url").String()
275+
}
276+
}
277+
278+
if imageURL == "" {
279+
imageURL = part.Get("url").String()
280+
}
281+
282+
if imageURL == "" {
283+
return "", false
284+
}
285+
286+
imageContent := `{"type":"image_url","image_url":{"url":""}}`
287+
imageContent, _ = sjson.Set(imageContent, "image_url.url", imageURL)
288+
289+
return imageContent, true
290+
291+
default:
292+
return "", false
293+
}
294+
}

0 commit comments

Comments
 (0)