@@ -88,6 +88,8 @@ absl::Status OpenAIResponsesHandler::parseInput(std::optional<std::string> allow
8888 return absl::InvalidArgumentError (" Messages array cannot be empty" );
8989 }
9090
91+ std::string pendingReasoning;
92+
9193 for (size_t i = 0 ; i < inputIt->value .GetArray ().Size (); ++i) {
9294 auto & item = inputIt->value .GetArray ()[i];
9395 if (!item.IsObject ()) {
@@ -101,8 +103,23 @@ absl::Status OpenAIResponsesHandler::parseInput(std::optional<std::string> allow
101103 const std::string itemType = (itemTypeIt != itemObj.MemberEnd () && itemTypeIt->value .IsString ())
102104 ? itemTypeIt->value .GetString () : " " ;
103105
104- // Skip reasoning items — they are internal chain-of-thought and not passed to the model
106+ // Parse reasoning items — extract summary text and buffer for the next assistant message
105107 if (itemType == " reasoning" ) {
108+ auto summaryIt = itemObj.FindMember (" summary" );
109+ if (summaryIt != itemObj.MemberEnd () && summaryIt->value .IsArray ()) {
110+ for (const auto & summaryItem : summaryIt->value .GetArray ()) {
111+ if (!summaryItem.IsObject ()) continue ;
112+ auto stTypeIt = summaryItem.GetObject ().FindMember (" type" );
113+ if (stTypeIt == summaryItem.GetObject ().MemberEnd () || !stTypeIt->value .IsString ()) continue ;
114+ if (std::string (stTypeIt->value .GetString ()) == " summary_text" ) {
115+ auto textIt = summaryItem.GetObject ().FindMember (" text" );
116+ if (textIt != summaryItem.GetObject ().MemberEnd () && textIt->value .IsString ()) {
117+ if (!pendingReasoning.empty ()) pendingReasoning += " \n " ;
118+ pendingReasoning += textIt->value .GetString ();
119+ }
120+ }
121+ }
122+ }
106123 continue ;
107124 }
108125
@@ -113,6 +130,10 @@ absl::Status OpenAIResponsesHandler::parseInput(std::optional<std::string> allow
113130 request.chatHistory .push_back ({});
114131 request.chatHistory .last ()[" role" ] = " assistant" ;
115132 request.chatHistory .last ()[" content" ] = " " ;
133+ if (!pendingReasoning.empty ()) {
134+ request.chatHistory .last ()[" reasoning_content" ] = pendingReasoning;
135+ pendingReasoning.clear ();
136+ }
116137 continue ;
117138 }
118139
@@ -139,6 +160,10 @@ absl::Status OpenAIResponsesHandler::parseInput(std::optional<std::string> allow
139160
140161 request.chatHistory .push_back ({});
141162 request.chatHistory .last ()[" role" ] = roleIt->value .GetString ();
163+ if (!pendingReasoning.empty ()) {
164+ request.chatHistory .last ()[" reasoning_content" ] = pendingReasoning;
165+ pendingReasoning.clear ();
166+ }
142167
143168 auto contentIt = itemObj.FindMember (" content" );
144169 if (contentIt == itemObj.MemberEnd ()) {
@@ -285,6 +310,7 @@ absl::Status OpenAIResponsesHandler::parseResponsesPart(std::optional<uint32_t>
285310 if (inputArrIt != doc.MemberEnd () && inputArrIt->value .IsArray ()) {
286311 // Pending function_call items to be merged into the next assistant message
287312 std::vector<const rapidjson::Value*> pendingFunctionCalls;
313+ std::string pendingReasoningJson;
288314
289315 // Helper: flush pending function_calls as an assistant message with the given text content
290316 auto flushPendingFunctionCalls = [&](const std::string& textContent) {
@@ -294,6 +320,10 @@ absl::Status OpenAIResponsesHandler::parseResponsesPart(std::optional<uint32_t>
294320 Value msgObj (kObjectType );
295321 msgObj.AddMember (" role" , Value (" assistant" , alloc), alloc);
296322 msgObj.AddMember (" content" , Value (textContent.c_str (), alloc), alloc);
323+ if (!pendingReasoningJson.empty ()) {
324+ msgObj.AddMember (" reasoning_content" , Value (pendingReasoningJson.c_str (), alloc), alloc);
325+ pendingReasoningJson.clear ();
326+ }
297327 Value toolCallsArray (kArrayType );
298328 for (const auto * fc : pendingFunctionCalls) {
299329 auto fcObj = fc->GetObject ();
@@ -351,8 +381,23 @@ absl::Status OpenAIResponsesHandler::parseResponsesPart(std::optional<uint32_t>
351381 const std::string itemType = (itemTypeIt != itemObj.MemberEnd () && itemTypeIt->value .IsString ())
352382 ? itemTypeIt->value .GetString () : " " ;
353383
354- // Skip reasoning items
384+ // Parse reasoning items — extract summary text and buffer for the next assistant message
355385 if (itemType == " reasoning" ) {
386+ auto summaryIt = itemObj.FindMember (" summary" );
387+ if (summaryIt != itemObj.MemberEnd () && summaryIt->value .IsArray ()) {
388+ for (const auto & summaryItem : summaryIt->value .GetArray ()) {
389+ if (!summaryItem.IsObject ()) continue ;
390+ auto stTypeIt = summaryItem.GetObject ().FindMember (" type" );
391+ if (stTypeIt == summaryItem.GetObject ().MemberEnd () || !stTypeIt->value .IsString ()) continue ;
392+ if (std::string (stTypeIt->value .GetString ()) == " summary_text" ) {
393+ auto textIt = summaryItem.GetObject ().FindMember (" text" );
394+ if (textIt != summaryItem.GetObject ().MemberEnd () && textIt->value .IsString ()) {
395+ if (!pendingReasoningJson.empty ()) pendingReasoningJson += " \n " ;
396+ pendingReasoningJson += textIt->value .GetString ();
397+ }
398+ }
399+ }
400+ }
356401 continue ;
357402 }
358403
@@ -401,6 +446,10 @@ absl::Status OpenAIResponsesHandler::parseResponsesPart(std::optional<uint32_t>
401446 Value msgObj (kObjectType );
402447 msgObj.AddMember (" role" , Value (" assistant" , alloc), alloc);
403448 msgObj.AddMember (" content" , Value (contentText.c_str (), alloc), alloc);
449+ if (!pendingReasoningJson.empty ()) {
450+ msgObj.AddMember (" reasoning_content" , Value (pendingReasoningJson.c_str (), alloc), alloc);
451+ pendingReasoningJson.clear ();
452+ }
404453 messagesArray.PushBack (msgObj, alloc);
405454 }
406455 } else {
@@ -419,11 +468,39 @@ absl::Status OpenAIResponsesHandler::parseResponsesPart(std::optional<uint32_t>
419468
420469 processedDoc.AddMember (" messages" , messagesArray, alloc);
421470
422- // Copy tools from original doc if present
471+ // Convert tools from Responses API flat format to chat/completions nested format.
472+ // Responses API: {"type": "function", "name": "foo", "description": "...", "parameters": {...}}
473+ // Chat/completions: {"type": "function", "function": {"name": "foo", "description": "...", "parameters": {...}}}
423474 auto toolsIt = doc.FindMember (" tools" );
424- if (toolsIt != doc.MemberEnd () && !toolsIt->value .IsNull ()) {
425- Value toolsCopy (toolsIt->value , alloc);
426- processedDoc.AddMember (" tools" , toolsCopy, alloc);
475+ if (toolsIt != doc.MemberEnd () && !toolsIt->value .IsNull () && toolsIt->value .IsArray ()) {
476+ Value toolsArray (kArrayType );
477+ for (const auto & tool : toolsIt->value .GetArray ()) {
478+ if (!tool.IsObject ()) continue ;
479+ auto toolObj = tool.GetObject ();
480+ // Check if this tool already has a nested "function" key (chat/completions format)
481+ if (toolObj.FindMember (" function" ) != toolObj.MemberEnd ()) {
482+ // Already in chat/completions format — copy as-is
483+ Value toolCopy (tool, alloc);
484+ toolsArray.PushBack (toolCopy, alloc);
485+ } else {
486+ // Responses API flat format — wrap under "function" key
487+ Value convertedTool (kObjectType );
488+ convertedTool.AddMember (" type" , Value (" function" , alloc), alloc);
489+ Value funcObj (kObjectType );
490+ // Copy all fields except "type" and "response" into the nested function object
491+ for (auto it2 = toolObj.MemberBegin (); it2 != toolObj.MemberEnd (); ++it2) {
492+ if (!it2->name .IsString ()) continue ;
493+ const std::string fieldName = it2->name .GetString ();
494+ if (fieldName == " type" || fieldName == " response" ) continue ;
495+ Value keyCopy (it2->name , alloc);
496+ Value valCopy (it2->value , alloc);
497+ funcObj.AddMember (keyCopy, valCopy, alloc);
498+ }
499+ convertedTool.AddMember (" function" , funcObj, alloc);
500+ toolsArray.PushBack (convertedTool, alloc);
501+ }
502+ }
503+ processedDoc.AddMember (" tools" , toolsArray, alloc);
427504 }
428505
429506 // Copy chat_template_kwargs from original doc if present
0 commit comments