@@ -168,6 +168,19 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
168168 }
169169
170170 // input array processing
171+ var pendingReasoningParts []string
172+ flushPendingReasoning := func () {
173+ if len (pendingReasoningParts ) == 0 {
174+ return
175+ }
176+ asst := []byte (`{"role":"assistant","content":[]}` )
177+ for _ , partJSON := range pendingReasoningParts {
178+ asst , _ = sjson .SetRawBytes (asst , "content.-1" , []byte (partJSON ))
179+ }
180+ out , _ = sjson .SetRawBytes (out , "messages.-1" , asst )
181+ pendingReasoningParts = nil
182+ }
183+
171184 if input := root .Get ("input" ); input .Exists () && input .IsArray () {
172185 input .ForEach (func (_ , item gjson.Result ) bool {
173186 if extractedFromSystem && strings .EqualFold (item .Get ("role" ).String (), "system" ) {
@@ -279,10 +292,26 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
279292 }
280293 }
281294
295+ hasReasoningParts := false
296+ if len (pendingReasoningParts ) > 0 {
297+ if role == "assistant" {
298+ if len (partsJSON ) == 0 && textAggregate .Len () > 0 {
299+ contentPart := []byte (`{"type":"text","text":""}` )
300+ contentPart , _ = sjson .SetBytes (contentPart , "text" , textAggregate .String ())
301+ partsJSON = append (partsJSON , string (contentPart ))
302+ }
303+ partsJSON = append (append ([]string {}, pendingReasoningParts ... ), partsJSON ... )
304+ pendingReasoningParts = nil
305+ hasReasoningParts = true
306+ } else {
307+ flushPendingReasoning ()
308+ }
309+ }
310+
282311 if len (partsJSON ) > 0 {
283312 msg := []byte (`{"role":"","content":[]}` )
284313 msg , _ = sjson .SetBytes (msg , "role" , role )
285- if len (partsJSON ) == 1 && ! hasImage && ! hasFile {
314+ if len (partsJSON ) == 1 && ! hasImage && ! hasFile && ! hasReasoningParts {
286315 // Preserve legacy behavior for single text content
287316 msg , _ = sjson .DeleteBytes (msg , "content" )
288317 textPart := gjson .Parse (partsJSON [0 ])
@@ -300,6 +329,11 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
300329 out , _ = sjson .SetRawBytes (out , "messages.-1" , msg )
301330 }
302331
332+ case "reasoning" :
333+ if thinkingPart := convertResponsesReasoningToClaudeThinking (item ); len (thinkingPart ) > 0 {
334+ pendingReasoningParts = append (pendingReasoningParts , string (thinkingPart ))
335+ }
336+
303337 case "function_call" :
304338 // Map to assistant tool_use
305339 callID := item .Get ("call_id" ).String ()
@@ -320,10 +354,15 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
320354 }
321355
322356 asst := []byte (`{"role":"assistant","content":[]}` )
357+ for _ , partJSON := range pendingReasoningParts {
358+ asst , _ = sjson .SetRawBytes (asst , "content.-1" , []byte (partJSON ))
359+ }
360+ pendingReasoningParts = nil
323361 asst , _ = sjson .SetRawBytes (asst , "content.-1" , toolUse )
324362 out , _ = sjson .SetRawBytes (out , "messages.-1" , asst )
325363
326364 case "function_call_output" :
365+ flushPendingReasoning ()
327366 // Map to user tool_result
328367 callID := item .Get ("call_id" ).String ()
329368 outputStr := item .Get ("output" ).String ()
@@ -338,6 +377,7 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
338377 return true
339378 })
340379 }
380+ flushPendingReasoning ()
341381
342382 includedToolNames := map [string ]struct {}{}
343383 toolNameMap := map [string ]string {}
@@ -398,6 +438,34 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
398438 return out
399439}
400440
441+ func convertResponsesReasoningToClaudeThinking (item gjson.Result ) []byte {
442+ signature := item .Get ("encrypted_content" ).String ()
443+ if signature == "" {
444+ return nil
445+ }
446+
447+ thinkingText := responsesReasoningSummaryText (item )
448+ thinkingPart := []byte (`{"type":"thinking","thinking":"","signature":""}` )
449+ thinkingPart , _ = sjson .SetBytes (thinkingPart , "thinking" , thinkingText )
450+ thinkingPart , _ = sjson .SetBytes (thinkingPart , "signature" , signature )
451+ return thinkingPart
452+ }
453+
454+ func responsesReasoningSummaryText (item gjson.Result ) string {
455+ var builder strings.Builder
456+ if summary := item .Get ("summary" ); summary .Exists () && summary .IsArray () {
457+ summary .ForEach (func (_ , part gjson.Result ) bool {
458+ if text := part .Get ("text" ); text .Exists () {
459+ builder .WriteString (text .String ())
460+ } else if part .Type == gjson .String {
461+ builder .WriteString (part .String ())
462+ }
463+ return true
464+ })
465+ }
466+ return builder .String ()
467+ }
468+
401469func convertResponsesToolToClaudeTools (tool gjson.Result , toolNameMap map [string ]string ) [][]byte {
402470 toolType := strings .TrimSpace (tool .Get ("type" ).String ())
403471 switch toolType {
0 commit comments