11using System . Buffers ;
2+ using System . Buffers . Text ;
23using System . ComponentModel ;
34using System . Diagnostics ;
45using System . Diagnostics . CodeAnalysis ;
@@ -98,7 +99,7 @@ internal Converter(bool materializeUtf8TextContentBlocks) =>
9899 string ? type = null ;
99100 ReadOnlyMemory < byte > ? utf8Text = null ;
100101 string ? name = null ;
101- string ? data = null ;
102+ ReadOnlyMemory < byte > ? dataUtf8 = null ;
102103 string ? mimeType = null ;
103104 string ? uri = null ;
104105 string ? description = null ;
@@ -141,7 +142,7 @@ internal Converter(bool materializeUtf8TextContentBlocks) =>
141142 break ;
142143
143144 case "data" :
144- data = reader . GetString ( ) ;
145+ dataUtf8 = ReadUtf8StringValueAsBytes ( ref reader ) ;
145146 break ;
146147
147148 case "mimeType" :
@@ -229,13 +230,13 @@ internal Converter(bool materializeUtf8TextContentBlocks) =>
229230
230231 "image" => new ImageContentBlock
231232 {
232- Data = data ?? throw new JsonException ( "Image data must be provided for 'image' type." ) ,
233+ DataUtf8 = dataUtf8 ?? throw new JsonException ( "Image data must be provided for 'image' type." ) ,
233234 MimeType = mimeType ?? throw new JsonException ( "MIME type must be provided for 'image' type." ) ,
234235 } ,
235236
236237 "audio" => new AudioContentBlock
237238 {
238- Data = data ?? throw new JsonException ( "Audio data must be provided for 'audio' type." ) ,
239+ DataUtf8 = dataUtf8 ?? throw new JsonException ( "Audio data must be provided for 'audio' type." ) ,
239240 MimeType = mimeType ?? throw new JsonException ( "MIME type must be provided for 'audio' type." ) ,
240241 } ,
241242
@@ -277,7 +278,7 @@ internal Converter(bool materializeUtf8TextContentBlocks) =>
277278 return block ;
278279 }
279280
280- private static ReadOnlyMemory < byte > ReadUtf8StringValueAsBytes ( ref Utf8JsonReader reader )
281+ internal static ReadOnlyMemory < byte > ReadUtf8StringValueAsBytes ( ref Utf8JsonReader reader )
281282 {
282283 if ( reader . TokenType != JsonTokenType . String )
283284 {
@@ -488,12 +489,26 @@ public override void Write(Utf8JsonWriter writer, ContentBlock value, JsonSerial
488489 break ;
489490
490491 case ImageContentBlock imageContent :
491- writer . WriteString ( "data" , imageContent . Data ) ;
492+ if ( imageContent . HasDataUtf8 )
493+ {
494+ writer . WriteString ( "data" , imageContent . GetDataUtf8Span ( ) ) ;
495+ }
496+ else
497+ {
498+ writer . WriteString ( "data" , imageContent . Data ) ;
499+ }
492500 writer . WriteString ( "mimeType" , imageContent . MimeType ) ;
493501 break ;
494502
495503 case AudioContentBlock audioContent :
496- writer . WriteString ( "data" , audioContent . Data ) ;
504+ if ( audioContent . HasDataUtf8 )
505+ {
506+ writer . WriteString ( "data" , audioContent . GetDataUtf8Span ( ) ) ;
507+ }
508+ else
509+ {
510+ writer . WriteString ( "data" , audioContent . Data ) ;
511+ }
497512 writer . WriteString ( "mimeType" , audioContent . MimeType ) ;
498513 break ;
499514
@@ -675,9 +690,9 @@ public static implicit operator Utf8TextContentBlock(TextContentBlock text)
675690[ DebuggerDisplay ( "{DebuggerDisplay,nq}" ) ]
676691public sealed class ImageContentBlock : ContentBlock
677692{
678- private byte [ ] ? _dataUtf8 ;
679- private byte [ ] ? _decodedData ;
680- private string _data = string . Empty ;
693+ private ReadOnlyMemory < byte > _dataUtf8 ;
694+ private ReadOnlyMemory < byte > _decodedData ;
695+ private string ? _data ;
681696
682697 /// <inheritdoc/>
683698 public override string Type => "image" ;
@@ -686,14 +701,16 @@ public sealed class ImageContentBlock : ContentBlock
686701 /// Gets or sets the base64-encoded image data.
687702 /// </summary>
688703 [ JsonPropertyName ( "data" ) ]
689- public required string Data
704+ public string Data
690705 {
691- get => _data ;
706+ get => _data ??= ! _dataUtf8 . IsEmpty
707+ ? Core . McpTextUtilities . GetStringFromUtf8 ( _dataUtf8 . Span )
708+ : string . Empty ;
692709 set
693710 {
694711 _data = value ;
695- _dataUtf8 = null ;
696- _decodedData = null ;
712+ _dataUtf8 = System . Text . Encoding . UTF8 . GetBytes ( value ) ;
713+ _decodedData = default ; // Invalidate cache
697714 }
698715 }
699716
@@ -703,20 +720,54 @@ public required string Data
703720 [ JsonIgnore ]
704721 public ReadOnlyMemory < byte > DataUtf8
705722 {
706- get => _dataUtf8 ??= System . Text . Encoding . UTF8 . GetBytes ( Data ) ;
723+ get => _dataUtf8 . IsEmpty
724+ ? _data is null
725+ ? ReadOnlyMemory < byte > . Empty
726+ : System . Text . Encoding . UTF8 . GetBytes ( _data )
727+ : _dataUtf8 ;
707728 set
708729 {
709- _dataUtf8 = value . Span . ToArray ( ) ;
710- _data = System . Text . Encoding . UTF8 . GetString ( _dataUtf8 ) ;
711- _decodedData = null ;
730+ _data = null ;
731+ _dataUtf8 = value ;
732+ _decodedData = default ; // Invalidate cache
712733 }
713734 }
714735
715736 /// <summary>
716- /// Gets the decoded image data represented by <see cref="Data "/>.
737+ /// Gets the decoded image data represented by <see cref="DataUtf8 "/>.
717738 /// </summary>
739+ /// <remarks>
740+ /// Accessing this member will decode the value in <see cref="DataUtf8"/> and cache the result.
741+ /// Subsequent accesses return the cached value unless <see cref="Data"/> or <see cref="DataUtf8"/> is modified.
742+ /// </remarks>
718743 [ JsonIgnore ]
719- public ReadOnlyMemory < byte > DecodedData => _decodedData ??= Convert . FromBase64String ( Data ) ;
744+ public ReadOnlyMemory < byte > DecodedData
745+ {
746+ get
747+ {
748+ if ( _decodedData . IsEmpty )
749+ {
750+ if ( _data is not null )
751+ {
752+ _decodedData = Convert . FromBase64String ( _data ) ;
753+ return _decodedData ;
754+ }
755+
756+ int maxLength = Base64 . GetMaxDecodedFromUtf8Length ( DataUtf8 . Length ) ;
757+ byte [ ] buffer = new byte [ maxLength ] ;
758+ if ( Base64 . DecodeFromUtf8 ( DataUtf8 . Span , buffer , out _ , out int bytesWritten ) == OperationStatus . Done )
759+ {
760+ _decodedData = bytesWritten == maxLength ? buffer : buffer . AsMemory ( 0 , bytesWritten ) . ToArray ( ) ;
761+ }
762+ else
763+ {
764+ throw new FormatException ( "Invalid base64 data" ) ;
765+ }
766+ }
767+
768+ return _decodedData ;
769+ }
770+ }
720771
721772 /// <summary>
722773 /// Gets or sets the MIME type (or "media type") of the content, specifying the format of the data.
@@ -727,6 +778,10 @@ public ReadOnlyMemory<byte> DataUtf8
727778 [ JsonPropertyName ( "mimeType" ) ]
728779 public required string MimeType { get ; set ; }
729780
781+ internal bool HasDataUtf8 => ! _dataUtf8 . IsEmpty ;
782+
783+ internal ReadOnlySpan < byte > GetDataUtf8Span ( ) => _dataUtf8 . Span ;
784+
730785 [ DebuggerBrowsable ( DebuggerBrowsableState . Never ) ]
731786 private string DebuggerDisplay => $ "MimeType = { MimeType } , Length = { DebuggerDisplayHelper . GetBase64LengthDisplay ( Data ) } ";
732787}
@@ -735,9 +790,9 @@ public ReadOnlyMemory<byte> DataUtf8
735790[ DebuggerDisplay ( "{DebuggerDisplay,nq}" ) ]
736791public sealed class AudioContentBlock : ContentBlock
737792{
738- private byte [ ] ? _dataUtf8 ;
739- private byte [ ] ? _decodedData ;
740- private string _data = string . Empty ;
793+ private ReadOnlyMemory < byte > _dataUtf8 ;
794+ private ReadOnlyMemory < byte > _decodedData ;
795+ private string ? _data ;
741796
742797 /// <inheritdoc/>
743798 public override string Type => "audio" ;
@@ -746,14 +801,16 @@ public sealed class AudioContentBlock : ContentBlock
746801 /// Gets or sets the base64-encoded audio data.
747802 /// </summary>
748803 [ JsonPropertyName ( "data" ) ]
749- public required string Data
804+ public string Data
750805 {
751- get => _data ;
806+ get => _data ??= ! _dataUtf8 . IsEmpty
807+ ? Core . McpTextUtilities . GetStringFromUtf8 ( _dataUtf8 . Span )
808+ : string . Empty ;
752809 set
753810 {
754811 _data = value ;
755- _dataUtf8 = null ;
756- _decodedData = null ;
812+ _dataUtf8 = System . Text . Encoding . UTF8 . GetBytes ( value ) ;
813+ _decodedData = default ; // Invalidate cache
757814 }
758815 }
759816
@@ -763,20 +820,54 @@ public required string Data
763820 [ JsonIgnore ]
764821 public ReadOnlyMemory < byte > DataUtf8
765822 {
766- get => _dataUtf8 ??= System . Text . Encoding . UTF8 . GetBytes ( Data ) ;
823+ get => _dataUtf8 . IsEmpty
824+ ? _data is null
825+ ? ReadOnlyMemory < byte > . Empty
826+ : System . Text . Encoding . UTF8 . GetBytes ( _data )
827+ : _dataUtf8 ;
767828 set
768829 {
769- _dataUtf8 = value . Span . ToArray ( ) ;
770- _data = System . Text . Encoding . UTF8 . GetString ( _dataUtf8 ) ;
771- _decodedData = null ;
830+ _data = null ;
831+ _dataUtf8 = value ;
832+ _decodedData = default ; // Invalidate cache
772833 }
773834 }
774835
775836 /// <summary>
776- /// Gets the decoded audio data represented by <see cref="Data "/>.
837+ /// Gets the decoded audio data represented by <see cref="DataUtf8 "/>.
777838 /// </summary>
839+ /// <remarks>
840+ /// Accessing this member will decode the value in <see cref="DataUtf8"/> and cache the result.
841+ /// Subsequent accesses return the cached value unless <see cref="Data"/> or <see cref="DataUtf8"/> is modified.
842+ /// </remarks>
778843 [ JsonIgnore ]
779- public ReadOnlyMemory < byte > DecodedData => _decodedData ??= Convert . FromBase64String ( Data ) ;
844+ public ReadOnlyMemory < byte > DecodedData
845+ {
846+ get
847+ {
848+ if ( _decodedData . IsEmpty )
849+ {
850+ if ( _data is not null )
851+ {
852+ _decodedData = Convert . FromBase64String ( _data ) ;
853+ return _decodedData ;
854+ }
855+
856+ int maxLength = Base64 . GetMaxDecodedFromUtf8Length ( DataUtf8 . Length ) ;
857+ byte [ ] buffer = new byte [ maxLength ] ;
858+ if ( Base64 . DecodeFromUtf8 ( DataUtf8 . Span , buffer , out _ , out int bytesWritten ) == OperationStatus . Done )
859+ {
860+ _decodedData = bytesWritten == maxLength ? buffer : buffer . AsMemory ( 0 , bytesWritten ) . ToArray ( ) ;
861+ }
862+ else
863+ {
864+ throw new FormatException ( "Invalid base64 data" ) ;
865+ }
866+ }
867+
868+ return _decodedData ;
869+ }
870+ }
780871
781872 /// <summary>
782873 /// Gets or sets the MIME type (or "media type") of the content, specifying the format of the data.
@@ -787,6 +878,10 @@ public ReadOnlyMemory<byte> DataUtf8
787878 [ JsonPropertyName ( "mimeType" ) ]
788879 public required string MimeType { get ; set ; }
789880
881+ internal bool HasDataUtf8 => ! _dataUtf8 . IsEmpty ;
882+
883+ internal ReadOnlySpan < byte > GetDataUtf8Span ( ) => _dataUtf8 . Span ;
884+
790885 [ DebuggerBrowsable ( DebuggerBrowsableState . Never ) ]
791886 private string DebuggerDisplay => $ "MimeType = { MimeType } , Length = { DebuggerDisplayHelper . GetBase64LengthDisplay ( Data ) } ";
792887}
0 commit comments