@@ -296,17 +296,17 @@ internal static async IAsyncEnumerable<ChatResponseUpdate> FromOpenAIStreamingRe
296296 ChatResponseUpdate CreateUpdate ( AIContent ? content = null ) =>
297297 new ( lastRole , content is not null ? [ content ] : null )
298298 {
299+ ContinuationToken = CreateContinuationToken (
300+ responseId ! ,
301+ latestResponseStatus ,
302+ options ? . BackgroundModeEnabled ,
303+ streamingUpdate . SequenceNumber ) ,
299304 ConversationId = conversationId ,
300305 CreatedAt = createdAt ,
301306 MessageId = lastMessageId ,
302307 ModelId = modelId ,
303308 RawRepresentation = streamingUpdate ,
304309 ResponseId = responseId ,
305- ContinuationToken = CreateContinuationToken (
306- responseId ! ,
307- latestResponseStatus ,
308- options ? . BackgroundModeEnabled ,
309- streamingUpdate . SequenceNumber )
310310 } ;
311311
312312 switch ( streamingUpdate )
@@ -395,48 +395,74 @@ ChatResponseUpdate CreateUpdate(AIContent? content = null) =>
395395 yield return CreateUpdate ( new TextReasoningContent ( reasoningTextDeltaUpdate . Delta ) ) ;
396396 break ;
397397
398- case StreamingResponseOutputItemDoneUpdate outputItemDoneUpdate when outputItemDoneUpdate . Item is FunctionCallResponseItem fcri :
399- yield return CreateUpdate ( OpenAIClientExtensions . ParseCallContent ( fcri . FunctionArguments . ToString ( ) , fcri . CallId , fcri . FunctionName ) ) ;
400- break ;
401-
402- case StreamingResponseOutputItemDoneUpdate outputItemDoneUpdate when outputItemDoneUpdate . Item is McpToolCallItem mtci :
403- var mcpUpdate = CreateUpdate ( ) ;
404- AddMcpToolCallContent ( mtci , mcpUpdate . Contents ) ;
405- yield return mcpUpdate ;
406- break ;
407-
408- case StreamingResponseOutputItemDoneUpdate outputItemDoneUpdate when outputItemDoneUpdate . Item is McpToolDefinitionListItem mtdli :
409- yield return CreateUpdate ( new AIContent { RawRepresentation = mtdli } ) ;
410- break ;
411-
412- case StreamingResponseOutputItemDoneUpdate outputItemDoneUpdate when outputItemDoneUpdate . Item is McpToolCallApprovalRequestItem mtcari :
413- yield return CreateUpdate ( new McpServerToolApprovalRequestContent ( mtcari . Id , new ( mtcari . Id , mtcari . ToolName , mtcari . ServerLabel )
414- {
415- Arguments = JsonSerializer . Deserialize ( mtcari . ToolArguments . ToMemory ( ) . Span , OpenAIJsonContext . Default . IReadOnlyDictionaryStringObject ) ! ,
416- RawRepresentation = mtcari ,
417- } )
398+ case StreamingResponseImageGenerationCallInProgressUpdate imageGenInProgress :
399+ yield return CreateUpdate ( new ImageGenerationToolCallContent
418400 {
419- RawRepresentation = mtcari ,
401+ ImageId = imageGenInProgress . ItemId ,
402+ RawRepresentation = imageGenInProgress ,
420403 } ) ;
421404 break ;
422405
423- case StreamingResponseOutputItemDoneUpdate outputItemDoneUpdate when outputItemDoneUpdate . Item is CodeInterpreterCallResponseItem cicri :
424- var codeUpdate = CreateUpdate ( ) ;
425- AddCodeInterpreterContents ( cicri , codeUpdate . Contents ) ;
426- yield return codeUpdate ;
406+ case StreamingResponseImageGenerationCallPartialImageUpdate streamingImageGenUpdate :
407+ yield return CreateUpdate ( GetImageGenerationResult ( streamingImageGenUpdate , options ) ) ;
427408 break ;
428409
429- case StreamingResponseOutputItemDoneUpdate outputItemDoneUpdate when
430- outputItemDoneUpdate . Item is MessageResponseItem mri &&
431- mri . Content is { Count : > 0 } content &&
432- content . Any ( c => c . OutputTextAnnotations is { Count : > 0 } ) :
433- AIContent annotatedContent = new ( ) ;
434- foreach ( var c in content )
410+ case StreamingResponseOutputItemDoneUpdate outputItemDoneUpdate :
411+ switch ( outputItemDoneUpdate . Item )
435412 {
436- PopulateAnnotations ( c , annotatedContent ) ;
437- }
413+ // Translate completed ResponseItems into their corresponding abstraction representations.
414+ case FunctionCallResponseItem fcri :
415+ yield return CreateUpdate ( OpenAIClientExtensions . ParseCallContent ( fcri . FunctionArguments . ToString ( ) , fcri . CallId , fcri . FunctionName ) ) ;
416+ break ;
417+
418+ case McpToolCallItem mtci :
419+ var mcpUpdate = CreateUpdate ( ) ;
420+ AddMcpToolCallContent ( mtci , mcpUpdate . Contents ) ;
421+ yield return mcpUpdate ;
422+ break ;
438423
439- yield return CreateUpdate ( annotatedContent ) ;
424+ case McpToolCallApprovalRequestItem mtcari :
425+ yield return CreateUpdate ( new McpServerToolApprovalRequestContent ( mtcari . Id , new ( mtcari . Id , mtcari . ToolName , mtcari . ServerLabel )
426+ {
427+ Arguments = JsonSerializer . Deserialize ( mtcari . ToolArguments . ToMemory ( ) . Span , OpenAIJsonContext . Default . IReadOnlyDictionaryStringObject ) ! ,
428+ RawRepresentation = mtcari ,
429+ } )
430+ {
431+ RawRepresentation = mtcari ,
432+ } ) ;
433+ break ;
434+
435+ case CodeInterpreterCallResponseItem cicri :
436+ var codeUpdate = CreateUpdate ( ) ;
437+ AddCodeInterpreterContents ( cicri , codeUpdate . Contents ) ;
438+ yield return codeUpdate ;
439+ break ;
440+
441+ // MessageResponseItems will have already had their content yielded as part of delta updates.
442+ // However, those deltas didn't yield annotations. If there are any annotations, yield them now.
443+ case MessageResponseItem mri when mri . Content is { Count : > 0 } mriContent && mriContent . Any ( c => c . OutputTextAnnotations is { Count : > 0 } ) :
444+ AIContent annotatedContent = new ( ) ; // do not include RawRepresentation to avoid duplication with already yielded deltas
445+ foreach ( var c in mriContent )
446+ {
447+ PopulateAnnotations ( c , annotatedContent ) ;
448+ }
449+ yield return CreateUpdate ( annotatedContent ) ;
450+ break ;
451+
452+ // For ResponseItems where we've already yielded partial deltas for the whole content,
453+ // we still want to yield an update, but we don't want it to include the ResponseItem
454+ // as the RawRepresentation, since if it did, when roundtripping we'd end up sending
455+ // the same content twice (first from the deltas, then from the raw response item).
456+ // Just yield an update without AIContent for the ResponseItem.
457+ case MessageResponseItem or ReasoningResponseItem or ImageGenerationCallResponseItem :
458+ yield return CreateUpdate ( ) ;
459+ break ;
460+
461+ // For everything else, yield an AIContent for the ResponseItem.
462+ default :
463+ yield return CreateUpdate ( new AIContent { RawRepresentation = outputItemDoneUpdate . Item } ) ;
464+ break ;
465+ }
440466 break ;
441467
442468 case StreamingResponseErrorUpdate errorUpdate :
@@ -454,18 +480,6 @@ outputItemDoneUpdate.Item is MessageResponseItem mri &&
454480 } ) ;
455481 break ;
456482
457- case StreamingResponseImageGenerationCallInProgressUpdate imageGenInProgress :
458- yield return CreateUpdate ( new ImageGenerationToolCallContent
459- {
460- ImageId = imageGenInProgress . ItemId ,
461- RawRepresentation = imageGenInProgress ,
462- } ) ;
463- goto default ;
464-
465- case StreamingResponseImageGenerationCallPartialImageUpdate streamingImageGenUpdate :
466- yield return CreateUpdate ( GetImageGenerationResult ( streamingImageGenUpdate , options ) ) ;
467- break ;
468-
469483 default :
470484 yield return CreateUpdate ( ) ;
471485 break ;
@@ -1299,8 +1313,8 @@ private static ImageGenerationToolResultContent GetImageGenerationResult(Streami
12991313 {
13001314 ImageId = update . ItemId ,
13011315 RawRepresentation = update ,
1302- Outputs = new List < AIContent >
1303- {
1316+ Outputs =
1317+ [
13041318 new DataContent ( update . PartialImageBytes , $ "image/{ outputType } ")
13051319 {
13061320 AdditionalProperties = new ( )
@@ -1310,7 +1324,7 @@ private static ImageGenerationToolResultContent GetImageGenerationResult(Streami
13101324 [ nameof ( update . PartialImageIndex ) ] = update . PartialImageIndex
13111325 }
13121326 }
1313- }
1327+ ]
13141328 } ;
13151329 }
13161330
0 commit comments