@@ -92,6 +92,7 @@ public sealed class Converter : JsonConverter<ContentBlock>
9292 string ? name = null ;
9393 string ? title = null ;
9494 ReadOnlyMemory < byte > ? data = null ;
95+ ReadOnlyMemory < byte > ? decodedData = null ;
9596 string ? mimeType = null ;
9697 string ? uri = null ;
9798 string ? description = null ;
@@ -137,7 +138,14 @@ public sealed class Converter : JsonConverter<ContentBlock>
137138 break ;
138139
139140 case "data" :
140- data = reader . HasValueSequence ? reader . ValueSequence . ToArray ( ) : reader . ValueSpan . ToArray ( ) ;
141+ if ( ! reader . ValueIsEscaped )
142+ {
143+ data = reader . HasValueSequence ? reader . ValueSequence . ToArray ( ) : reader . ValueSpan . ToArray ( ) ;
144+ }
145+ else
146+ {
147+ decodedData = reader . GetBytesFromBase64 ( ) ;
148+ }
141149 break ;
142150
143151 case "mimeType" :
@@ -230,17 +238,23 @@ public sealed class Converter : JsonConverter<ContentBlock>
230238 Text = text ?? throw new JsonException ( "Text contents must be provided for 'text' type." ) ,
231239 } ,
232240
233- "image" => new ImageContentBlock
234- {
235- Data = data ?? throw new JsonException ( "Image data must be provided for 'image' type." ) ,
236- MimeType = mimeType ?? throw new JsonException ( "MIME type must be provided for 'image' type." ) ,
237- } ,
238-
239- "audio" => new AudioContentBlock
240- {
241- Data = data ?? throw new JsonException ( "Audio data must be provided for 'audio' type." ) ,
242- MimeType = mimeType ?? throw new JsonException ( "MIME type must be provided for 'audio' type." ) ,
243- } ,
241+ "image" => decodedData is not null ?
242+ ImageContentBlock . FromBytes ( decodedData . Value ,
243+ mimeType ?? throw new JsonException ( "MIME type must be provided for 'image' type." ) ) :
244+ new ImageContentBlock
245+ {
246+ Data = data ?? throw new JsonException ( "Image data must be provided for 'image' type." ) ,
247+ MimeType = mimeType ?? throw new JsonException ( "MIME type must be provided for 'image' type." ) ,
248+ } ,
249+
250+ "audio" => decodedData is not null ?
251+ AudioContentBlock . FromBytes ( decodedData . Value ,
252+ mimeType ?? throw new JsonException ( "MIME type must be provided for 'audio' type." ) ) :
253+ new AudioContentBlock
254+ {
255+ Data = data ?? throw new JsonException ( "Audio data must be provided for 'audio' type." ) ,
256+ MimeType = mimeType ?? throw new JsonException ( "MIME type must be provided for 'audio' type." ) ,
257+ } ,
244258
245259 "resource" => new EmbeddedResourceBlock
246260 {
@@ -414,7 +428,7 @@ public sealed class TextContentBlock : ContentBlock
414428public sealed class ImageContentBlock : ContentBlock
415429{
416430 private ReadOnlyMemory < byte > ? _decodedData ;
417- private ReadOnlyMemory < byte > _data ;
431+ private ReadOnlyMemory < byte > ? _data ;
418432
419433 /// <summary>
420434 /// Creates an <see cref="ImageContentBlock"/> from decoded image bytes.
@@ -423,22 +437,27 @@ public sealed class ImageContentBlock : ContentBlock
423437 /// <param name="mimeType">The MIME type of the image.</param>
424438 /// <returns>A new <see cref="ImageContentBlock"/> instance.</returns>
425439 /// <remarks>
426- /// This method stores the provided bytes as <see cref="DecodedData"/> and encodes them to base64 UTF-8 bytes for <see cref="Data"/>.
440+ /// This method stores the provided bytes as <see cref="DecodedData"/> and lazily encodes them to base64 UTF-8 bytes for <see cref="Data"/>.
427441 /// </remarks>
428442 /// <exception cref="ArgumentNullException"><paramref name="mimeType"/> is <see langword="null"/>.</exception>
429443 /// <exception cref="ArgumentException"><paramref name="mimeType"/> is empty or composed entirely of whitespace.</exception>
430444 public static ImageContentBlock FromBytes ( ReadOnlyMemory < byte > bytes , string mimeType )
431445 {
432446 Throw . IfNullOrWhiteSpace ( mimeType ) ;
433447
434- ReadOnlyMemory < byte > data = EncodingUtilities . EncodeToBase64Utf8 ( bytes ) ;
435-
436- return new ( )
437- {
438- _decodedData = bytes ,
439- Data = data ,
440- MimeType = mimeType
441- } ;
448+ return new ( bytes , mimeType ) ;
449+ }
450+
451+ /// <summary>Initializes a new instance of the <see cref="ImageContentBlock"/> class.</summary>
452+ public ImageContentBlock ( )
453+ {
454+ }
455+
456+ [ SetsRequiredMembers ]
457+ private ImageContentBlock ( ReadOnlyMemory < byte > decodedData , string mimeType )
458+ {
459+ _decodedData = decodedData ;
460+ MimeType = mimeType ;
442461 }
443462
444463 /// <inheritdoc/>
@@ -453,7 +472,16 @@ public static ImageContentBlock FromBytes(ReadOnlyMemory<byte> bytes, string mim
453472 [ JsonPropertyName ( "data" ) ]
454473 public required ReadOnlyMemory < byte > Data
455474 {
456- get => _data ;
475+ get
476+ {
477+ if ( _data is null )
478+ {
479+ Debug . Assert ( _decodedData is not null ) ;
480+ _data = EncodingUtilities . EncodeToBase64Utf8 ( _decodedData ! . Value ) ;
481+ }
482+
483+ return _data . Value ;
484+ }
457485 set
458486 {
459487 _data = value ;
@@ -494,15 +522,22 @@ public ReadOnlyMemory<byte> DecodedData
494522 public required string MimeType { get ; set ; }
495523
496524 [ DebuggerBrowsable ( DebuggerBrowsableState . Never ) ]
497- private string DebuggerDisplay => $ "MimeType = { MimeType } , Length = { DebuggerDisplayHelper . GetBase64LengthDisplay ( Data ) } ";
525+ private string DebuggerDisplay
526+ {
527+ get
528+ {
529+ string lengthDisplay = _decodedData is not null ? $ "{ _decodedData . Value . Length } bytes" : DebuggerDisplayHelper . GetBase64LengthDisplay ( Data ) ;
530+ return $ "MimeType = { MimeType } , Length = { lengthDisplay } ";
531+ }
532+ }
498533}
499534
500535/// <summary>Represents audio provided to or from an LLM.</summary>
501536[ DebuggerDisplay ( "{DebuggerDisplay,nq}" ) ]
502537public sealed class AudioContentBlock : ContentBlock
503538{
504539 private ReadOnlyMemory < byte > ? _decodedData ;
505- private ReadOnlyMemory < byte > _data ;
540+ private ReadOnlyMemory < byte > ? _data ;
506541
507542 /// <summary>
508543 /// Creates an <see cref="AudioContentBlock"/> from decoded audio bytes.
@@ -511,22 +546,27 @@ public sealed class AudioContentBlock : ContentBlock
511546 /// <param name="mimeType">The MIME type of the audio.</param>
512547 /// <returns>A new <see cref="AudioContentBlock"/> instance.</returns>
513548 /// <remarks>
514- /// This method stores the provided bytes as <see cref="DecodedData"/> and encodes them to base64 UTF-8 bytes for <see cref="Data"/>.
549+ /// This method stores the provided bytes as <see cref="DecodedData"/> and lazily encodes them to base64 UTF-8 bytes for <see cref="Data"/>.
515550 /// </remarks>
516551 /// <exception cref="ArgumentNullException"><paramref name="mimeType"/> is <see langword="null"/>.</exception>
517552 /// <exception cref="ArgumentException"><paramref name="mimeType"/> is empty or composed entirely of whitespace.</exception>
518553 public static AudioContentBlock FromBytes ( ReadOnlyMemory < byte > bytes , string mimeType )
519554 {
520555 Throw . IfNullOrWhiteSpace ( mimeType ) ;
521556
522- ReadOnlyMemory < byte > data = EncodingUtilities . EncodeToBase64Utf8 ( bytes ) ;
523-
524- return new ( )
525- {
526- _decodedData = bytes ,
527- Data = data ,
528- MimeType = mimeType
529- } ;
557+ return new ( bytes , mimeType ) ;
558+ }
559+
560+ /// <summary>Initializes a new instance of the <see cref="AudioContentBlock"/> class.</summary>
561+ public AudioContentBlock ( )
562+ {
563+ }
564+
565+ [ SetsRequiredMembers ]
566+ private AudioContentBlock ( ReadOnlyMemory < byte > decodedData , string mimeType )
567+ {
568+ _decodedData = decodedData ;
569+ MimeType = mimeType ;
530570 }
531571
532572 /// <inheritdoc/>
@@ -541,7 +581,16 @@ public static AudioContentBlock FromBytes(ReadOnlyMemory<byte> bytes, string mim
541581 [ JsonPropertyName ( "data" ) ]
542582 public required ReadOnlyMemory < byte > Data
543583 {
544- get => _data ;
584+ get
585+ {
586+ if ( _data is null )
587+ {
588+ Debug . Assert ( _decodedData is not null ) ;
589+ _data = EncodingUtilities . EncodeToBase64Utf8 ( _decodedData ! . Value ) ;
590+ }
591+
592+ return _data . Value ;
593+ }
545594 set
546595 {
547596 _data = value ;
@@ -582,7 +631,14 @@ public ReadOnlyMemory<byte> DecodedData
582631 public required string MimeType { get ; set ; }
583632
584633 [ DebuggerBrowsable ( DebuggerBrowsableState . Never ) ]
585- private string DebuggerDisplay => $ "MimeType = { MimeType } , Length = { DebuggerDisplayHelper . GetBase64LengthDisplay ( Data ) } ";
634+ private string DebuggerDisplay
635+ {
636+ get
637+ {
638+ string lengthDisplay = _decodedData is not null ? $ "{ _decodedData . Value . Length } bytes" : DebuggerDisplayHelper . GetBase64LengthDisplay ( Data ) ;
639+ return $ "MimeType = { MimeType } , Length = { lengthDisplay } ";
640+ }
641+ }
586642}
587643
588644/// <summary>Represents the contents of a resource, embedded into a prompt or tool call result.</summary>
0 commit comments