@@ -8,7 +8,6 @@ package claude
88import (
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