@@ -402,6 +402,280 @@ public void ToChatMessage_CallToolResult_WithAnonymousTypeInContent_Works()
402402
403403 Assert . Null ( exception ) ;
404404 }
405+
406+ /// <summary>
407+ /// Provides test data for base64 AIContent roundtrip tests covering various lengths,
408+ /// the full base64 alphabet (including '+' and '/'), and with/without padding.
409+ /// </summary>
410+ public static TheoryData < byte [ ] > Base64TestData ( )
411+ {
412+ var data = new TheoryData < byte [ ] >
413+ {
414+ new byte [ ] { 0x00 } , // 1 byte, 2 padding chars: "AA=="
415+ new byte [ ] { 0x00 , 0x01 } , // 2 bytes, 1 padding char: "AAE="
416+ new byte [ ] { 0x00 , 0x01 , 0x02 } , // 3 bytes, no padding: "AAEC"
417+ new byte [ ] { 0xFF , 0xD8 , 0xFF , 0xE0 } , // produces '/' in base64: "/9j/4A=="
418+ new byte [ ] { 0xFB , 0xEF , 0xBE } , // produces '+' in base64: "++++"
419+ } ;
420+
421+ // All 256 byte values to exercise the full base64 alphabet
422+ byte [ ] allBytes = new byte [ 256 ] ;
423+ for ( int i = 0 ; i < 256 ; i ++ )
424+ {
425+ allBytes [ i ] = ( byte ) i ;
426+ }
427+ data . Add ( allBytes ) ;
428+
429+ return data ;
430+ }
431+
432+ [ Theory ]
433+ [ MemberData ( nameof ( Base64TestData ) ) ]
434+ public void ImageContentBlock_ToAIContent_PreservesData ( byte [ ] originalBytes )
435+ {
436+ var image = ImageContentBlock . FromBytes ( originalBytes , "image/png" ) ;
437+
438+ var aiContent = image . ToAIContent ( ) ;
439+
440+ var dataContent = Assert . IsType < DataContent > ( aiContent ) ;
441+ Assert . Equal ( "image/png" , dataContent . MediaType ) ;
442+ Assert . Equal ( originalBytes , dataContent . Data . ToArray ( ) ) ;
443+ }
444+
445+ [ Theory ]
446+ [ MemberData ( nameof ( Base64TestData ) ) ]
447+ public void ImageContentBlock_ToAIContent_RoundTrips ( byte [ ] originalBytes )
448+ {
449+ var image = ImageContentBlock . FromBytes ( originalBytes , "image/png" ) ;
450+
451+ var aiContent = Assert . IsType < DataContent > ( image . ToAIContent ( ) ) ;
452+ var roundTripped = Assert . IsType < ImageContentBlock > ( aiContent . ToContentBlock ( ) ) ;
453+
454+ Assert . Equal ( "image/png" , roundTripped . MimeType ) ;
455+ Assert . Equal ( originalBytes , roundTripped . DecodedData . ToArray ( ) ) ;
456+ }
457+
458+ [ Theory ]
459+ [ MemberData ( nameof ( Base64TestData ) ) ]
460+ public void ImageContentBlock_DataSetter_ToAIContent_RoundTrips ( byte [ ] originalBytes )
461+ {
462+ string base64 = Convert . ToBase64String ( originalBytes ) ;
463+ var image = new ImageContentBlock
464+ {
465+ Data = System . Text . Encoding . UTF8 . GetBytes ( base64 ) ,
466+ MimeType = "image/jpeg"
467+ } ;
468+
469+ var aiContent = Assert . IsType < DataContent > ( image . ToAIContent ( ) ) ;
470+ Assert . Equal ( originalBytes , aiContent . Data . ToArray ( ) ) ;
471+
472+ var roundTripped = Assert . IsType < ImageContentBlock > ( aiContent . ToContentBlock ( ) ) ;
473+ Assert . Equal ( "image/jpeg" , roundTripped . MimeType ) ;
474+ Assert . Equal ( originalBytes , roundTripped . DecodedData . ToArray ( ) ) ;
475+ }
476+
477+ [ Theory ]
478+ [ MemberData ( nameof ( Base64TestData ) ) ]
479+ public void AudioContentBlock_ToAIContent_PreservesData ( byte [ ] originalBytes )
480+ {
481+ var audio = AudioContentBlock . FromBytes ( originalBytes , "audio/wav" ) ;
482+
483+ var aiContent = audio . ToAIContent ( ) ;
484+
485+ var dataContent = Assert . IsType < DataContent > ( aiContent ) ;
486+ Assert . Equal ( "audio/wav" , dataContent . MediaType ) ;
487+ Assert . Equal ( originalBytes , dataContent . Data . ToArray ( ) ) ;
488+ }
489+
490+ [ Theory ]
491+ [ MemberData ( nameof ( Base64TestData ) ) ]
492+ public void AudioContentBlock_ToAIContent_RoundTrips ( byte [ ] originalBytes )
493+ {
494+ var audio = AudioContentBlock . FromBytes ( originalBytes , "audio/wav" ) ;
495+
496+ var aiContent = Assert . IsType < DataContent > ( audio . ToAIContent ( ) ) ;
497+ var roundTripped = Assert . IsType < AudioContentBlock > ( aiContent . ToContentBlock ( ) ) ;
498+
499+ Assert . Equal ( "audio/wav" , roundTripped . MimeType ) ;
500+ Assert . Equal ( originalBytes , roundTripped . DecodedData . ToArray ( ) ) ;
501+ }
502+
503+ [ Theory ]
504+ [ MemberData ( nameof ( Base64TestData ) ) ]
505+ public void AudioContentBlock_DataSetter_ToAIContent_RoundTrips ( byte [ ] originalBytes )
506+ {
507+ string base64 = Convert . ToBase64String ( originalBytes ) ;
508+ var audio = new AudioContentBlock
509+ {
510+ Data = System . Text . Encoding . UTF8 . GetBytes ( base64 ) ,
511+ MimeType = "audio/mp3"
512+ } ;
513+
514+ var aiContent = Assert . IsType < DataContent > ( audio . ToAIContent ( ) ) ;
515+ Assert . Equal ( originalBytes , aiContent . Data . ToArray ( ) ) ;
516+
517+ var roundTripped = Assert . IsType < AudioContentBlock > ( aiContent . ToContentBlock ( ) ) ;
518+ Assert . Equal ( "audio/mp3" , roundTripped . MimeType ) ;
519+ Assert . Equal ( originalBytes , roundTripped . DecodedData . ToArray ( ) ) ;
520+ }
521+
522+ [ Theory ]
523+ [ MemberData ( nameof ( Base64TestData ) ) ]
524+ public void BlobResourceContents_ToAIContent_PreservesData ( byte [ ] originalBytes )
525+ {
526+ var blob = BlobResourceContents . FromBytes ( originalBytes , "file:///test.bin" , "application/octet-stream" ) ;
527+ var embedded = new EmbeddedResourceBlock { Resource = blob } ;
528+
529+ var aiContent = embedded . ToAIContent ( ) ;
530+
531+ var dataContent = Assert . IsType < DataContent > ( aiContent ) ;
532+ Assert . Equal ( "application/octet-stream" , dataContent . MediaType ) ;
533+ Assert . Equal ( originalBytes , dataContent . Data . ToArray ( ) ) ;
534+ }
535+
536+ [ Theory ]
537+ [ MemberData ( nameof ( Base64TestData ) ) ]
538+ public void BlobResourceContents_ToAIContent_RoundTrips ( byte [ ] originalBytes )
539+ {
540+ var blob = BlobResourceContents . FromBytes ( originalBytes , "file:///test.bin" , "application/octet-stream" ) ;
541+ var embedded = new EmbeddedResourceBlock { Resource = blob } ;
542+
543+ var aiContent = Assert . IsType < DataContent > ( embedded . ToAIContent ( ) ) ;
544+ var roundTripped = Assert . IsType < EmbeddedResourceBlock > ( aiContent . ToContentBlock ( ) ) ;
545+ var roundTrippedBlob = Assert . IsType < BlobResourceContents > ( roundTripped . Resource ) ;
546+
547+ Assert . Equal ( "application/octet-stream" , roundTrippedBlob . MimeType ) ;
548+ Assert . Equal ( originalBytes , roundTrippedBlob . DecodedData . ToArray ( ) ) ;
549+ }
550+
551+ [ Theory ]
552+ [ MemberData ( nameof ( Base64TestData ) ) ]
553+ public void BlobResourceContents_BlobSetter_ToAIContent_RoundTrips ( byte [ ] originalBytes )
554+ {
555+ string base64 = Convert . ToBase64String ( originalBytes ) ;
556+ var blob = new BlobResourceContents
557+ {
558+ Blob = System . Text . Encoding . UTF8 . GetBytes ( base64 ) ,
559+ Uri = "file:///test.bin" ,
560+ MimeType = "application/octet-stream"
561+ } ;
562+ var embedded = new EmbeddedResourceBlock { Resource = blob } ;
563+
564+ var aiContent = Assert . IsType < DataContent > ( embedded . ToAIContent ( ) ) ;
565+ Assert . Equal ( originalBytes , aiContent . Data . ToArray ( ) ) ;
566+
567+ var roundTripped = Assert . IsType < EmbeddedResourceBlock > ( aiContent . ToContentBlock ( ) ) ;
568+ var roundTrippedBlob = Assert . IsType < BlobResourceContents > ( roundTripped . Resource ) ;
569+ Assert . Equal ( "application/octet-stream" , roundTrippedBlob . MimeType ) ;
570+ Assert . Equal ( originalBytes , roundTrippedBlob . DecodedData . ToArray ( ) ) ;
571+ }
572+
573+ [ Theory ]
574+ [ MemberData ( nameof ( Base64TestData ) ) ]
575+ public void DataContent_ImageToContentBlock_PreservesData ( byte [ ] originalBytes )
576+ {
577+ var dataContent = new DataContent ( originalBytes , "image/png" ) ;
578+
579+ var contentBlock = dataContent . ToContentBlock ( ) ;
580+
581+ var image = Assert . IsType < ImageContentBlock > ( contentBlock ) ;
582+ Assert . Equal ( "image/png" , image . MimeType ) ;
583+ Assert . Equal ( originalBytes , image . DecodedData . ToArray ( ) ) ;
584+ }
585+
586+ [ Theory ]
587+ [ MemberData ( nameof ( Base64TestData ) ) ]
588+ public void DataContent_AudioToContentBlock_PreservesData ( byte [ ] originalBytes )
589+ {
590+ var dataContent = new DataContent ( originalBytes , "audio/wav" ) ;
591+
592+ var contentBlock = dataContent . ToContentBlock ( ) ;
593+
594+ var audio = Assert . IsType < AudioContentBlock > ( contentBlock ) ;
595+ Assert . Equal ( "audio/wav" , audio . MimeType ) ;
596+ Assert . Equal ( originalBytes , audio . DecodedData . ToArray ( ) ) ;
597+ }
598+
599+ [ Theory ]
600+ [ MemberData ( nameof ( Base64TestData ) ) ]
601+ public void DataContent_BlobToContentBlock_PreservesData ( byte [ ] originalBytes )
602+ {
603+ var dataContent = new DataContent ( originalBytes , "application/octet-stream" ) ;
604+
605+ var contentBlock = dataContent . ToContentBlock ( ) ;
606+
607+ var embedded = Assert . IsType < EmbeddedResourceBlock > ( contentBlock ) ;
608+ var blob = Assert . IsType < BlobResourceContents > ( embedded . Resource ) ;
609+ Assert . Equal ( "application/octet-stream" , blob . MimeType ) ;
610+ Assert . Equal ( originalBytes , blob . DecodedData . ToArray ( ) ) ;
611+ }
612+
613+ [ Theory ]
614+ [ MemberData ( nameof ( Base64TestData ) ) ]
615+ public void ImageContentBlock_JsonDeserialized_ToAIContent_RoundTrips ( byte [ ] originalBytes )
616+ {
617+ string base64 = Convert . ToBase64String ( originalBytes ) ;
618+ string json = $$ """ {"type":"image","data":"{{ base64 }} ","mimeType":"image/png"}""" ;
619+
620+ var image = Assert . IsType < ImageContentBlock > (
621+ JsonSerializer . Deserialize < ContentBlock > ( json , McpJsonUtilities . DefaultOptions ) ) ;
622+ var aiContent = Assert . IsType < DataContent > ( image . ToAIContent ( ) ) ;
623+ Assert . Equal ( originalBytes , aiContent . Data . ToArray ( ) ) ;
624+
625+ var roundTripped = Assert . IsType < ImageContentBlock > ( aiContent . ToContentBlock ( ) ) ;
626+ Assert . Equal ( originalBytes , roundTripped . DecodedData . ToArray ( ) ) ;
627+ }
628+
629+ [ Theory ]
630+ [ MemberData ( nameof ( Base64TestData ) ) ]
631+ public void ImageContentBlock_EscapedJsonDeserialized_ToAIContent_RoundTrips ( byte [ ] originalBytes )
632+ {
633+ string base64 = Convert . ToBase64String ( originalBytes ) ;
634+ string json = $$ """ {"type":"image","data":"{{ base64 . Replace ( "/" , "\\ /" ) }} ","mimeType":"image/png"}""" ;
635+
636+ var image = Assert . IsType < ImageContentBlock > (
637+ JsonSerializer . Deserialize < ContentBlock > ( json , McpJsonUtilities . DefaultOptions ) ) ;
638+ var aiContent = Assert . IsType < DataContent > ( image . ToAIContent ( ) ) ;
639+ Assert . Equal ( originalBytes , aiContent . Data . ToArray ( ) ) ;
640+
641+ var roundTripped = Assert . IsType < ImageContentBlock > ( aiContent . ToContentBlock ( ) ) ;
642+ Assert . Equal ( originalBytes , roundTripped . DecodedData . ToArray ( ) ) ;
643+ }
644+
645+ [ Theory ]
646+ [ MemberData ( nameof ( Base64TestData ) ) ]
647+ public void AudioContentBlock_EscapedJsonDeserialized_ToAIContent_RoundTrips ( byte [ ] originalBytes )
648+ {
649+ string base64 = Convert . ToBase64String ( originalBytes ) ;
650+ string json = $$ """ {"type":"audio","data":"{{ base64 . Replace ( "/" , "\\ /" ) }} ","mimeType":"audio/wav"}""" ;
651+
652+ var audio = Assert . IsType < AudioContentBlock > (
653+ JsonSerializer . Deserialize < ContentBlock > ( json , McpJsonUtilities . DefaultOptions ) ) ;
654+ var aiContent = Assert . IsType < DataContent > ( audio . ToAIContent ( ) ) ;
655+ Assert . Equal ( originalBytes , aiContent . Data . ToArray ( ) ) ;
656+
657+ var roundTripped = Assert . IsType < AudioContentBlock > ( aiContent . ToContentBlock ( ) ) ;
658+ Assert . Equal ( originalBytes , roundTripped . DecodedData . ToArray ( ) ) ;
659+ }
660+
661+ [ Theory ]
662+ [ MemberData ( nameof ( Base64TestData ) ) ]
663+ public void BlobResourceContents_EscapedJsonDeserialized_ToAIContent_RoundTrips ( byte [ ] originalBytes )
664+ {
665+ string base64 = Convert . ToBase64String ( originalBytes ) ;
666+ string json = $$ """ {"uri":"file:///test.bin","blob":"{{ base64 . Replace ( "/" , "\\ /" ) }} ","mimeType":"application/octet-stream"}""" ;
667+
668+ var blob = Assert . IsType < BlobResourceContents > (
669+ JsonSerializer . Deserialize < ResourceContents > ( json , McpJsonUtilities . DefaultOptions ) ) ;
670+ var embedded = new EmbeddedResourceBlock { Resource = blob } ;
671+
672+ var aiContent = Assert . IsType < DataContent > ( embedded . ToAIContent ( ) ) ;
673+ Assert . Equal ( originalBytes , aiContent . Data . ToArray ( ) ) ;
674+
675+ var roundTripped = Assert . IsType < EmbeddedResourceBlock > ( aiContent . ToContentBlock ( ) ) ;
676+ var roundTrippedBlob = Assert . IsType < BlobResourceContents > ( roundTripped . Resource ) ;
677+ Assert . Equal ( originalBytes , roundTrippedBlob . DecodedData . ToArray ( ) ) ;
678+ }
405679}
406680
407681// Test type for named user-defined type test
0 commit comments