@@ -46,21 +46,21 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
4646 rootResult := gjson .ParseBytes (rawJSON )
4747 toolNameMap := buildReverseMapFromClaudeOriginalToShort (rawJSON )
4848 template , _ = sjson .SetBytes (template , "model" , modelName )
49+ inputItems := make ([][]byte , 0 , len (rootResult .Get ("messages" ).Array ())+ 1 )
4950
5051 // Process system messages and convert them to input content format.
5152 systemsResult := rootResult .Get ("system" )
5253 if systemsResult .Exists () {
53- message := []byte (`{"type":"message","role":"developer","content":[]}` )
54- contentIndex := 0
54+ contentItems := make ([][]byte , 0 , len (systemsResult .Array ()))
5555
5656 appendSystemText := func (text string ) {
5757 if text == "" || util .IsClaudeCodeAttributionSystemText (text ) {
5858 return
5959 }
6060
61- message , _ = sjson . SetBytes ( message , fmt . Sprintf ( "content.%d. type", contentIndex ), "input_text" )
62- message , _ = sjson .SetBytes (message , fmt . Sprintf ( "content.%d. text", contentIndex ) , text )
63- contentIndex ++
61+ content := [] byte ( `{" type": "input_text","text":""}` )
62+ content , _ = sjson .SetBytes (content , " text" , text )
63+ contentItems = append ( contentItems , content )
6464 }
6565
6666 if systemsResult .Type == gjson .String {
@@ -75,8 +75,10 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
7575 }
7676 }
7777
78- if contentIndex > 0 {
79- template , _ = sjson .SetRawBytes (template , "input.-1" , message )
78+ if len (contentItems ) > 0 {
79+ message := []byte (`{"type":"message","role":"developer"}` )
80+ message , _ = sjson .SetRawBytes (message , "content" , marshalRawJSONArray (contentItems ))
81+ inputItems = append (inputItems , message )
8082 }
8183 }
8284
@@ -92,27 +94,21 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
9294 if reminderText , ok := translatorcommon .ClaudeMessageSystemReminderText (messageResult .Get ("content" )); ok {
9395 message := []byte (`{"type":"message","role":"user","content":[{"type":"input_text","text":""}]}` )
9496 message , _ = sjson .SetBytes (message , "content.0.text" , reminderText )
95- template , _ = sjson . SetRawBytes ( template , "input.-1" , message )
97+ inputItems = append ( inputItems , message )
9698 }
9799 continue
98100 }
99101
100- newMessage := func () []byte {
101- msg := []byte (`{"type":"message","role":"","content":[]}` )
102- msg , _ = sjson .SetBytes (msg , "role" , messageRole )
103- return msg
104- }
105-
106- message := newMessage ()
107- contentIndex := 0
108- hasContent := false
102+ messageContentsResult := messageResult .Get ("content" )
103+ contentItems := make ([][]byte , 0 , len (messageContentsResult .Array ()))
109104
110105 flushMessage := func () {
111- if hasContent {
112- template , _ = sjson .SetRawBytes (template , "input.-1" , message )
113- message = newMessage ()
114- contentIndex = 0
115- hasContent = false
106+ if len (contentItems ) > 0 {
107+ message := []byte (`{"type":"message","role":""}` )
108+ message , _ = sjson .SetBytes (message , "role" , messageRole )
109+ message , _ = sjson .SetRawBytes (message , "content" , marshalRawJSONArray (contentItems ))
110+ inputItems = append (inputItems , message )
111+ contentItems = contentItems [:0 ]
116112 }
117113 }
118114
@@ -121,17 +117,16 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
121117 if messageRole == "assistant" {
122118 partType = "output_text"
123119 }
124- message , _ = sjson . SetBytes ( message , fmt . Sprintf ( "content.%d. type", contentIndex ), partType )
125- message , _ = sjson .SetBytes (message , fmt . Sprintf ( "content.%d.text " , contentIndex ), text )
126- contentIndex ++
127- hasContent = true
120+ content := [] byte ( `{" type":"","text":""}` )
121+ content , _ = sjson .SetBytes (content , "type " , partType )
122+ content , _ = sjson . SetBytes ( content , "text" , text )
123+ contentItems = append ( contentItems , content )
128124 }
129125
130126 appendImageContent := func (dataURL string ) {
131- message , _ = sjson .SetBytes (message , fmt .Sprintf ("content.%d.type" , contentIndex ), "input_image" )
132- message , _ = sjson .SetBytes (message , fmt .Sprintf ("content.%d.image_url" , contentIndex ), dataURL )
133- contentIndex ++
134- hasContent = true
127+ content := []byte (`{"type":"input_image","image_url":""}` )
128+ content , _ = sjson .SetBytes (content , "image_url" , dataURL )
129+ contentItems = append (contentItems , content )
135130 }
136131
137132 appendReasoningContent := func (part gjson.Result ) {
@@ -154,10 +149,9 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
154149 flushMessage ()
155150 reasoningItem := []byte (`{"type":"reasoning","summary":[],"content":null}` )
156151 reasoningItem , _ = sjson .SetBytes (reasoningItem , "encrypted_content" , signature )
157- template , _ = sjson . SetRawBytes ( template , "input.-1" , reasoningItem )
152+ inputItems = append ( inputItems , reasoningItem )
158153 }
159154
160- messageContentsResult := messageResult .Get ("content" )
161155 if messageContentsResult .IsArray () {
162156 messageContentResults := messageContentsResult .Array ()
163157 for j := 0 ; j < len (messageContentResults ); j ++ {
@@ -202,17 +196,16 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
202196 functionCallMessage , _ = sjson .SetBytes (functionCallMessage , "name" , name )
203197 }
204198 functionCallMessage , _ = sjson .SetBytes (functionCallMessage , "arguments" , messageContentResult .Get ("input" ).Raw )
205- template , _ = sjson . SetRawBytes ( template , "input.-1" , functionCallMessage )
199+ inputItems = append ( inputItems , functionCallMessage )
206200 case "tool_result" :
207201 flushMessage ()
208202 functionCallOutputMessage := []byte (`{"type":"function_call_output"}` )
209203 functionCallOutputMessage , _ = sjson .SetBytes (functionCallOutputMessage , "call_id" , shortenCodexCallIDIfNeeded (messageContentResult .Get ("tool_use_id" ).String ()))
210204
211205 contentResult := messageContentResult .Get ("content" )
212206 if contentResult .IsArray () {
213- toolResultContentIndex := 0
214- toolResultContent := []byte (`[]` )
215207 contentResults := contentResult .Array ()
208+ toolResultContentItems := make ([][]byte , 0 , len (contentResults ))
216209 for k := 0 ; k < len (contentResults ); k ++ {
217210 toolResultContentType := contentResults [k ].Get ("type" ).String ()
218211 if toolResultContentType == "image" {
@@ -232,27 +225,27 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
232225 }
233226 dataURL := fmt .Sprintf ("data:%s;base64,%s" , mediaType , data )
234227
235- toolResultContent , _ = sjson . SetBytes ( toolResultContent , fmt . Sprintf ( "%d. type", toolResultContentIndex ), "input_image" )
236- toolResultContent , _ = sjson .SetBytes (toolResultContent , fmt . Sprintf ( "%d. image_url", toolResultContentIndex ) , dataURL )
237- toolResultContentIndex ++
228+ toolResultContent := [] byte ( `{" type": "input_image","image_url":""}` )
229+ toolResultContent , _ = sjson .SetBytes (toolResultContent , " image_url" , dataURL )
230+ toolResultContentItems = append ( toolResultContentItems , toolResultContent )
238231 }
239232 }
240233 } else if toolResultContentType == "text" {
241- toolResultContent , _ = sjson . SetBytes ( toolResultContent , fmt . Sprintf ( "%d. type", toolResultContentIndex ), "input_text" )
242- toolResultContent , _ = sjson .SetBytes (toolResultContent , fmt . Sprintf ( "%d. text", toolResultContentIndex ) , contentResults [k ].Get ("text" ).String ())
243- toolResultContentIndex ++
234+ toolResultContent := [] byte ( `{" type": "input_text","text":""}` )
235+ toolResultContent , _ = sjson .SetBytes (toolResultContent , " text" , contentResults [k ].Get ("text" ).String ())
236+ toolResultContentItems = append ( toolResultContentItems , toolResultContent )
244237 }
245238 }
246- if toolResultContentIndex > 0 {
247- functionCallOutputMessage , _ = sjson .SetRawBytes (functionCallOutputMessage , "output" , toolResultContent )
239+ if len ( toolResultContentItems ) > 0 {
240+ functionCallOutputMessage , _ = sjson .SetRawBytes (functionCallOutputMessage , "output" , marshalRawJSONArray ( toolResultContentItems ) )
248241 } else {
249242 functionCallOutputMessage , _ = sjson .SetBytes (functionCallOutputMessage , "output" , messageContentResult .Get ("content" ).String ())
250243 }
251244 } else {
252245 functionCallOutputMessage , _ = sjson .SetBytes (functionCallOutputMessage , "output" , messageContentResult .Get ("content" ).String ())
253246 }
254247
255- template , _ = sjson . SetRawBytes ( template , "input.-1" , functionCallOutputMessage )
248+ inputItems = append ( inputItems , functionCallOutputMessage )
256249 }
257250 }
258251 flushMessage ()
@@ -266,16 +259,17 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
266259
267260 // Convert tools declarations to the expected format for the Codex API.
268261 toolsResult := rootResult .Get ("tools" )
262+ var toolItems [][]byte
269263 if toolsResult .IsArray () {
270- template , _ = sjson .SetRawBytes (template , "tools" , []byte (`[]` ))
271264 webSearchToolNames := buildClaudeWebSearchToolNameSet (toolsResult )
272265 template , _ = sjson .SetRawBytes (template , "tool_choice" , convertClaudeToolChoiceToCodex (rootResult .Get ("tool_choice" ), toolNameMap , webSearchToolNames ))
273266 toolResults := toolsResult .Array ()
267+ toolItems = make ([][]byte , 0 , len (toolResults ))
274268 for i := 0 ; i < len (toolResults ); i ++ {
275269 toolResult := toolResults [i ]
276270 // Special handling: map Claude web search tool to Codex web_search
277271 if isClaudeWebSearchToolType (toolResult .Get ("type" ).String ()) {
278- template , _ = sjson . SetRawBytes ( template , "tools.-1" , convertClaudeWebSearchToolToCodex (toolResult ))
272+ toolItems = append ( toolItems , convertClaudeWebSearchToolToCodex (toolResult ))
279273 continue
280274 }
281275 tool := []byte (toolResult .Raw )
@@ -296,7 +290,7 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
296290 tool , _ = sjson .DeleteBytes (tool , "cache_control" )
297291 tool , _ = sjson .DeleteBytes (tool , "defer_loading" )
298292 tool , _ = sjson .SetBytes (tool , "strict" , false )
299- template , _ = sjson . SetRawBytes ( template , "tools.-1" , tool )
293+ toolItems = append ( toolItems , tool )
300294 }
301295 }
302296
@@ -346,10 +340,35 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
346340 template , _ = sjson .SetBytes (template , "stream" , true )
347341 template , _ = sjson .SetBytes (template , "store" , false )
348342 template , _ = sjson .SetBytes (template , "include" , []string {"reasoning.encrypted_content" })
343+ if toolsResult .IsArray () {
344+ template , _ = sjson .SetRawBytes (template , "tools" , marshalRawJSONArray (toolItems ))
345+ }
346+ template , _ = sjson .SetRawBytes (template , "input" , marshalRawJSONArray (inputItems ))
349347
350348 return template
351349}
352350
351+ func marshalRawJSONArray (items [][]byte ) []byte {
352+ size := 2
353+ if len (items ) > 1 {
354+ size += len (items ) - 1
355+ }
356+ for i := 0 ; i < len (items ); i ++ {
357+ size += len (items [i ])
358+ }
359+
360+ result := make ([]byte , 0 , size )
361+ result = append (result , '[' )
362+ for i := 0 ; i < len (items ); i ++ {
363+ if i > 0 {
364+ result = append (result , ',' )
365+ }
366+ result = append (result , items [i ]... )
367+ }
368+ result = append (result , ']' )
369+ return result
370+ }
371+
353372func codexClaudeTargetAcceptsGrokSignature (modelName string ) bool {
354373 baseModel := strings .ToLower (strings .TrimSpace (thinking .ParseSuffix (modelName ).ModelName ))
355374 return strings .Contains (baseModel , "grok" )
0 commit comments