@@ -155,6 +155,83 @@ public void VerifyToFunctionCallContent()
155155 Assert . NotNull ( content . Arguments ) ;
156156 }
157157
158+ /// <summary>
159+ /// Verify that ReasoningResponseItem with SummaryParts generates ReasoningContent correctly.
160+ /// </summary>
161+ [ Fact ]
162+ public void VerifyToChatMessageContentWithReasoningResponseItem ( )
163+ {
164+ // Arrange
165+ var reasoningResponseItem = this . CreateReasoningResponseItem ( "Let me think about this step by step..." ) ;
166+
167+ // Act
168+ ChatMessageContent ? chatMessageContent = reasoningResponseItem . ToChatMessageContent ( ) ;
169+
170+ // Assert
171+ Assert . NotNull ( chatMessageContent ) ;
172+ Assert . Equal ( AuthorRole . Assistant , chatMessageContent . Role ) ;
173+ Assert . Single ( chatMessageContent . Items ) ;
174+
175+ var reasoningContent = chatMessageContent . Items [ 0 ] as ReasoningContent ;
176+ Assert . NotNull ( reasoningContent ) ;
177+ Assert . Equal ( "Let me think about this step by step..." , reasoningContent . Text ) ;
178+ }
179+
180+ /// <summary>
181+ /// Verify that ReasoningResponseItem converts to correct ChatMessageContentItemCollection with ReasoningContent.
182+ /// </summary>
183+ [ Fact ]
184+ public void VerifyToChatMessageContentItemCollectionWithReasoningResponseItem ( )
185+ {
186+ // Arrange
187+ var reasoningResponseItem = this . CreateReasoningResponseItem ( "Analyzing the problem..." ) ;
188+
189+ // Act
190+ ChatMessageContentItemCollection collection = reasoningResponseItem . ToChatMessageContentItemCollection ( ) ;
191+
192+ // Assert
193+ Assert . NotNull ( collection ) ;
194+ Assert . Single ( collection ) ;
195+ Assert . IsType < ReasoningContent > ( collection [ 0 ] ) ;
196+
197+ var reasoningContent = collection [ 0 ] as ReasoningContent ;
198+ Assert . Equal ( "Analyzing the problem..." , reasoningContent ? . Text ) ;
199+ }
200+
201+ /// <summary>
202+ /// Verify that OpenAIResponse with both ReasoningResponseItem and MessageResponseItem generates correct content types.
203+ /// </summary>
204+ [ Fact ]
205+ public void VerifyToChatMessageContentWithMixedResponseItems ( )
206+ {
207+ // Arrange
208+ var reasoningResponseItem = this . CreateReasoningResponseItem ( "Thinking about the answer..." ) ;
209+ var messageResponseItem = ResponseItem . CreateAssistantMessageItem ( "Here is my response." ) ;
210+
211+ OpenAIResponse mockResponse = this . CreateMockOpenAIResponse ( "gpt-4o-mini" ,
212+ [
213+ reasoningResponseItem ,
214+ messageResponseItem
215+ ] ) ;
216+
217+ // Act
218+ ChatMessageContent chatMessageContent = mockResponse . ToChatMessageContent ( ) ;
219+
220+ // Assert
221+ Assert . NotNull ( chatMessageContent ) ;
222+ Assert . Equal ( 2 , chatMessageContent . Items . Count ) ;
223+
224+ // First item should be ReasoningContent
225+ Assert . IsType < ReasoningContent > ( chatMessageContent . Items [ 0 ] ) ;
226+ var reasoningContent = chatMessageContent . Items [ 0 ] as ReasoningContent ;
227+ Assert . Equal ( "Thinking about the answer..." , reasoningContent ? . Text ) ;
228+
229+ // Second item should be TextContent
230+ Assert . IsType < TextContent > ( chatMessageContent . Items [ 1 ] ) ;
231+ var textContent = chatMessageContent . Items [ 1 ] as TextContent ;
232+ Assert . Equal ( "Here is my response." , textContent ? . Text ) ;
233+ }
234+
158235 #region private
159236 private OpenAIResponse CreateMockOpenAIResponse ( string model , IEnumerable < ResponseItem > outputItems )
160237 {
@@ -250,5 +327,112 @@ private OpenAIResponse CreateMockOpenAIResponse(string id, DateTimeOffset create
250327 throw new InvalidOperationException ( "Constructor not found." ) ;
251328 }
252329
330+ private ReasoningResponseItem CreateReasoningResponseItem ( string ? reasoningText = null , IReadOnlyList < ReasoningSummaryPart > ? summaryParts = null )
331+ {
332+ Type reasoningResponseItemType = typeof ( ReasoningResponseItem ) ;
333+ Type reasoningSummaryTextPartType = typeof ( ReasoningSummaryTextPart ) ;
334+
335+ // If reasoningText is provided and summaryParts is not, create summaryParts with the text
336+ if ( reasoningText != null && summaryParts == null )
337+ {
338+ // Try to find any public static factory method or constructor that can create ReasoningSummaryTextPart
339+ var createTextPartMethod = typeof ( ReasoningSummaryPart ) . GetMethod (
340+ "CreateTextPart" ,
341+ BindingFlags . Static | BindingFlags . Public ,
342+ null ,
343+ [ typeof ( string ) ] ,
344+ null ) ;
345+
346+ if ( createTextPartMethod != null )
347+ {
348+ var textPart = createTextPartMethod . Invoke ( null , [ reasoningText ] ) as ReasoningSummaryTextPart ;
349+ summaryParts = textPart != null ? new List < ReasoningSummaryPart > { textPart } : new List < ReasoningSummaryPart > ( ) ;
350+ }
351+ else
352+ {
353+ // Try to find constructor - search for all constructors
354+ var textPartConstructors = reasoningSummaryTextPartType . GetConstructors (
355+ BindingFlags . Instance | BindingFlags . NonPublic | BindingFlags . Public ) ;
356+
357+ ConstructorInfo ? textPartConstructor = null ;
358+ foreach ( var ctor in textPartConstructors )
359+ {
360+ var parameters = ctor . GetParameters ( ) ;
361+ if ( parameters . Length >= 1 && parameters [ 0 ] . ParameterType == typeof ( string ) )
362+ {
363+ textPartConstructor = ctor ;
364+ break ;
365+ }
366+ }
367+
368+ if ( textPartConstructor != null )
369+ {
370+ var ctorParams = textPartConstructor . GetParameters ( ) ;
371+ var args = new object ? [ ctorParams . Length ] ;
372+ args [ 0 ] = reasoningText ;
373+ // Fill in any additional parameters with null or default values
374+ for ( int i = 1 ; i < ctorParams . Length ; i ++ )
375+ {
376+ args [ i ] = null ;
377+ }
378+
379+ var textPart = textPartConstructor . Invoke ( args ) as ReasoningSummaryTextPart ;
380+ summaryParts = textPart != null ? new List < ReasoningSummaryPart > { textPart } : new List < ReasoningSummaryPart > ( ) ;
381+ }
382+ else
383+ {
384+ throw new InvalidOperationException ( "Could not find a way to create ReasoningSummaryTextPart." ) ;
385+ }
386+ }
387+ }
388+
389+ // Convert null summaryParts to empty list for method calls
390+ var partsToPass = summaryParts ?? new List < ReasoningSummaryPart > ( ) ;
391+
392+ // Try to find a static factory method first
393+ var createReasoningItemMethod = typeof ( ResponseItem ) . GetMethod (
394+ "CreateReasoningItem" ,
395+ BindingFlags . Static | BindingFlags . Public ,
396+ null ,
397+ [ typeof ( IEnumerable < ReasoningSummaryPart > ) ] ,
398+ null ) ;
399+
400+ if ( createReasoningItemMethod != null )
401+ {
402+ return ( ReasoningResponseItem ) createReasoningItemMethod . Invoke ( null , [ partsToPass ] ) ! ;
403+ }
404+
405+ // If no factory method, look for constructors
406+ var constructors = reasoningResponseItemType . GetConstructors (
407+ BindingFlags . Instance | BindingFlags . NonPublic | BindingFlags . Public ) ;
408+
409+ foreach ( var ctor in constructors )
410+ {
411+ var parameters = ctor . GetParameters ( ) ;
412+
413+ // Look for constructor that takes IReadOnlyList<ReasoningSummaryPart> or similar
414+ if ( parameters . Length >= 1 )
415+ {
416+ var firstParamType = parameters [ 0 ] . ParameterType ;
417+ if ( firstParamType . IsAssignableFrom ( typeof ( List < ReasoningSummaryPart > ) ) ||
418+ firstParamType . IsAssignableFrom ( typeof ( IReadOnlyList < ReasoningSummaryPart > ) ) ||
419+ firstParamType . IsAssignableFrom ( typeof ( IEnumerable < ReasoningSummaryPart > ) ) )
420+ {
421+ var args = new object ? [ parameters . Length ] ;
422+ args [ 0 ] = partsToPass ;
423+ // Fill in any additional parameters with null
424+ for ( int i = 1 ; i < parameters . Length ; i ++ )
425+ {
426+ args [ i ] = null ;
427+ }
428+
429+ return ( ReasoningResponseItem ) ctor . Invoke ( args ) ;
430+ }
431+ }
432+ }
433+
434+ throw new InvalidOperationException ( "Constructor not found for ReasoningResponseItem." ) ;
435+ }
436+
253437 #endregion
254438}
0 commit comments