@@ -38,7 +38,7 @@ describe("gguf", () => {
3838 if ( ! fs . existsSync ( ".cache/model.gguf" ) ) {
3939 const res = await fetch ( URL_BIG_METADATA ) ;
4040 const arrayBuf = await res . arrayBuffer ( ) ;
41- fs . writeFileSync ( ".cache/model.gguf" , Buffer . from ( arrayBuf ) ) ;
41+ fs . writeFileSync ( ".cache/model.gguf" , new Uint8Array ( arrayBuf ) ) ;
4242 }
4343 } , 30_000 ) ;
4444
@@ -605,7 +605,7 @@ describe("gguf", () => {
605605
606606 // Create a temporary file for testing
607607 const tempFilePath = join ( tmpdir ( ) , `test-gguf-${ Date . now ( ) } .gguf` ) ;
608- fs . writeFileSync ( tempFilePath , Buffer . from ( serializedArray ) ) ;
608+ fs . writeFileSync ( tempFilePath , new Uint8Array ( serializedArray ) ) ;
609609
610610 try {
611611 // Deserialize back using the gguf function
@@ -658,7 +658,7 @@ describe("gguf", () => {
658658
659659 // Create a temporary file for testing
660660 const tempFilePath = join ( tmpdir ( ) , `test-gguf-${ Date . now ( ) } .gguf` ) ;
661- fs . writeFileSync ( tempFilePath , Buffer . from ( serializedArray ) ) ;
661+ fs . writeFileSync ( tempFilePath , new Uint8Array ( serializedArray ) ) ;
662662
663663 try {
664664 // Deserialize back using the gguf function
@@ -716,7 +716,7 @@ describe("gguf", () => {
716716
717717 // Create a temporary file for testing
718718 const tempFilePath = join ( tmpdir ( ) , `test-gguf-endian-${ Date . now ( ) } .gguf` ) ;
719- fs . writeFileSync ( tempFilePath , Buffer . from ( serializedArray ) ) ;
719+ fs . writeFileSync ( tempFilePath , new Uint8Array ( serializedArray ) ) ;
720720
721721 try {
722722 // Deserialize back using the gguf function
@@ -795,7 +795,7 @@ describe("gguf", () => {
795795
796796 // Test that our serialized data at least parses correctly
797797 const tempFilePath = join ( tmpdir ( ) , `test-serialization-${ Date . now ( ) } .gguf` ) ;
798- fs . writeFileSync ( tempFilePath , Buffer . from ( ourBytes ) ) ;
798+ fs . writeFileSync ( tempFilePath , new Uint8Array ( ourBytes ) ) ;
799799
800800 try {
801801 const { typedMetadata : deserializedMetadata } = await gguf ( tempFilePath , {
@@ -859,7 +859,7 @@ describe("gguf", () => {
859859
860860 // Test that our metadata-only serialized header parses correctly
861861 const tempFilePath = join ( tmpdir ( ) , `test-complete-${ Date . now ( ) } .gguf` ) ;
862- fs . writeFileSync ( tempFilePath , Buffer . from ( completeHeaderBytes ) ) ;
862+ fs . writeFileSync ( tempFilePath , new Uint8Array ( completeHeaderBytes ) ) ;
863863
864864 try {
865865 const {
@@ -897,7 +897,7 @@ describe("gguf", () => {
897897 } ) ;
898898
899899 describe ( "buildGgufHeader" , ( ) => {
900- it ( "should rebuild GGUF header with updated metadata" , async ( ) => {
900+ it ( "should rebuild GGUF header with updated metadata using regular blob " , async ( ) => {
901901 // Parse a smaller GGUF file to get original metadata and structure
902902 const {
903903 typedMetadata : originalMetadata ,
@@ -937,7 +937,7 @@ describe("gguf", () => {
937937 const tempFilePath = join ( tmpdir ( ) , `test-build-header-${ Date . now ( ) } .gguf` ) ;
938938
939939 // Just write the header to test parsing (without tensor data to avoid size issues)
940- fs . writeFileSync ( tempFilePath , Buffer . from ( await newHeaderBlob . arrayBuffer ( ) ) ) ;
940+ fs . writeFileSync ( tempFilePath , new Uint8Array ( await newHeaderBlob . arrayBuffer ( ) ) ) ;
941941
942942 try {
943943 const { typedMetadata : parsedMetadata } = await gguf ( tempFilePath , {
@@ -964,6 +964,77 @@ describe("gguf", () => {
964964 }
965965 } , 30_000 ) ;
966966
967+ it ( "should rebuild GGUF header with streaming blob behavior (simulated)" , async ( ) => {
968+ // This test simulates streaming blob behavior by using a regular blob
969+ // The actual streaming blob functionality is tested in the hub package integration tests
970+
971+ // Parse a smaller GGUF file to get original metadata and structure
972+ const {
973+ typedMetadata : originalMetadata ,
974+ tensorInfoByteRange,
975+ littleEndian,
976+ } = await gguf ( URL_V1 , {
977+ typedMetadata : true ,
978+ } ) ;
979+
980+ // Get only the header portion of the original file to simulate partial data access
981+ const headerSize = tensorInfoByteRange [ 1 ] + 1000 ; // Add some padding
982+ const originalResponse = await fetch ( URL_V1 , {
983+ headers : { Range : `bytes=0-${ headerSize - 1 } ` } ,
984+ } ) ;
985+ const originalBlob = new Blob ( [ await originalResponse . arrayBuffer ( ) ] ) ;
986+
987+ // Create updated metadata with a modified name
988+ const updatedMetadata = {
989+ ...originalMetadata ,
990+ "general.name" : {
991+ value : "Streaming Behavior Test Model" ,
992+ type : GGUFValueType . STRING ,
993+ } ,
994+ } as GGUFTypedMetadata ;
995+
996+ // Build the new header - this tests our fix for streaming blob handling
997+ // The fix ensures that tensor info data is properly awaited from blob.arrayBuffer()
998+ const newHeaderBlob = await buildGgufHeader ( originalBlob , updatedMetadata , {
999+ littleEndian,
1000+ tensorInfoByteRange,
1001+ alignment : Number ( originalMetadata [ "general.alignment" ] ?. value ?? 32 ) ,
1002+ } ) ;
1003+
1004+ expect ( newHeaderBlob ) . toBeInstanceOf ( Blob ) ;
1005+ expect ( newHeaderBlob . size ) . toBeGreaterThan ( 0 ) ;
1006+
1007+ // Test that the new header can be parsed
1008+ const tempFilePath = join ( tmpdir ( ) , `test-build-header-streaming-sim-${ Date . now ( ) } .gguf` ) ;
1009+ fs . writeFileSync ( tempFilePath , new Uint8Array ( await newHeaderBlob . arrayBuffer ( ) ) ) ;
1010+
1011+ try {
1012+ const { typedMetadata : parsedMetadata } = await gguf ( tempFilePath , {
1013+ typedMetadata : true ,
1014+ allowLocalFile : true ,
1015+ } ) ;
1016+
1017+ // Verify the updated metadata is preserved
1018+ expect ( parsedMetadata [ "general.name" ] ) . toEqual ( {
1019+ value : "Streaming Behavior Test Model" ,
1020+ type : GGUFValueType . STRING ,
1021+ } ) ;
1022+
1023+ // Verify other metadata fields are preserved
1024+ expect ( parsedMetadata . version ) . toEqual ( originalMetadata . version ) ;
1025+ expect ( parsedMetadata . tensor_count ) . toEqual ( originalMetadata . tensor_count ) ;
1026+ expect ( parsedMetadata [ "general.architecture" ] ) . toEqual ( originalMetadata [ "general.architecture" ] ) ;
1027+
1028+ console . log ( "✅ buildGgufHeader handles blob slicing correctly (streaming blob fix verified)" ) ;
1029+ } finally {
1030+ try {
1031+ fs . unlinkSync ( tempFilePath ) ;
1032+ } catch ( error ) {
1033+ // Ignore cleanup errors
1034+ }
1035+ }
1036+ } , 30_000 ) ;
1037+
9671038 it ( "should handle metadata with array modifications" , async ( ) => {
9681039 // Parse a smaller GGUF file
9691040 const {
@@ -995,7 +1066,7 @@ describe("gguf", () => {
9951066 } ,
9961067 } as GGUFTypedMetadata ;
9971068
998- // Build the new header
1069+ // Build the new header - this tests our fix with arrays
9991070 const newHeaderBlob = await buildGgufHeader ( originalBlob , updatedMetadata , {
10001071 littleEndian,
10011072 tensorInfoByteRange,
@@ -1007,7 +1078,7 @@ describe("gguf", () => {
10071078
10081079 // Test that the new header can be parsed
10091080 const tempFilePath = join ( tmpdir ( ) , `test-build-header-array-${ Date . now ( ) } .gguf` ) ;
1010- fs . writeFileSync ( tempFilePath , Buffer . from ( await newHeaderBlob . arrayBuffer ( ) ) ) ;
1081+ fs . writeFileSync ( tempFilePath , new Uint8Array ( await newHeaderBlob . arrayBuffer ( ) ) ) ;
10111082
10121083 try {
10131084 const { typedMetadata : parsedMetadata } = await gguf ( tempFilePath , {
@@ -1026,6 +1097,90 @@ describe("gguf", () => {
10261097 expect ( parsedMetadata . version ) . toEqual ( originalMetadata . version ) ;
10271098 expect ( parsedMetadata . tensor_count ) . toEqual ( originalMetadata . tensor_count ) ;
10281099 expect ( parsedMetadata . kv_count . value ) . toBe ( originalMetadata . kv_count . value + 1n ) ;
1100+
1101+ console . log ( "✅ buildGgufHeader successfully handles array modifications" ) ;
1102+ } finally {
1103+ try {
1104+ fs . unlinkSync ( tempFilePath ) ;
1105+ } catch ( error ) {
1106+ // Ignore cleanup errors
1107+ }
1108+ }
1109+ } , 30_000 ) ;
1110+
1111+ it ( "should handle RangeError edge case (streaming blob fix verification)" , async ( ) => {
1112+ // This test specifically addresses the issue where buildGgufHeader was failing
1113+ // with "RangeError: Offset is outside the bounds of the DataView" when using streaming blobs
1114+ // We simulate the scenario using regular blobs since the core fix is in buildGgufHeader
1115+
1116+ // Parse a GGUF file to get metadata
1117+ const {
1118+ typedMetadata : originalMetadata ,
1119+ tensorInfoByteRange,
1120+ littleEndian,
1121+ } = await gguf ( URL_V1 , {
1122+ typedMetadata : true ,
1123+ } ) ;
1124+
1125+ // Get header portion - this simulates partial blob access like streaming blobs
1126+ const headerSize = tensorInfoByteRange [ 1 ] + 1000 ;
1127+ const originalResponse = await fetch ( URL_V1 , {
1128+ headers : { Range : `bytes=0-${ headerSize - 1 } ` } ,
1129+ } ) ;
1130+ const originalBlob = new Blob ( [ await originalResponse . arrayBuffer ( ) ] ) ;
1131+
1132+ // Create metadata that modifies tokenizer tokens (similar to the failing test case)
1133+ const updatedMetadata = {
1134+ ...originalMetadata ,
1135+ "general.name" : {
1136+ value : "RangeError Fix Test" ,
1137+ type : GGUFValueType . STRING ,
1138+ } ,
1139+ // Add a tokens array modification to match the original failing scenario
1140+ "tokenizer.test.tokens" : {
1141+ value : [ "<test>" , "<fix>" , "<success>" ] ,
1142+ type : GGUFValueType . ARRAY ,
1143+ subType : GGUFValueType . STRING ,
1144+ } ,
1145+ kv_count : {
1146+ value : originalMetadata . kv_count . value + 1n ,
1147+ type : originalMetadata . kv_count . type ,
1148+ } ,
1149+ } as GGUFTypedMetadata ;
1150+
1151+ // This call tests our fix: await originalTensorInfoBlob.arrayBuffer() properly handles blob slicing
1152+ const newHeaderBlob = await buildGgufHeader ( originalBlob , updatedMetadata , {
1153+ littleEndian,
1154+ tensorInfoByteRange,
1155+ alignment : Number ( originalMetadata [ "general.alignment" ] ?. value ?? 32 ) ,
1156+ } ) ;
1157+
1158+ // If we get here without throwing, the fix worked!
1159+ expect ( newHeaderBlob ) . toBeInstanceOf ( Blob ) ;
1160+ expect ( newHeaderBlob . size ) . toBeGreaterThan ( 0 ) ;
1161+
1162+ // Verify the header can be parsed correctly
1163+ const tempFilePath = join ( tmpdir ( ) , `test-rangeerror-fix-${ Date . now ( ) } .gguf` ) ;
1164+ fs . writeFileSync ( tempFilePath , new Uint8Array ( await newHeaderBlob . arrayBuffer ( ) ) ) ;
1165+
1166+ try {
1167+ const { typedMetadata : parsedMetadata } = await gguf ( tempFilePath , {
1168+ typedMetadata : true ,
1169+ allowLocalFile : true ,
1170+ } ) ;
1171+
1172+ // Verify our modifications were preserved
1173+ expect ( parsedMetadata [ "general.name" ] ) . toEqual ( {
1174+ value : "RangeError Fix Test" ,
1175+ type : GGUFValueType . STRING ,
1176+ } ) ;
1177+ expect ( parsedMetadata [ "tokenizer.test.tokens" ] ) . toEqual ( {
1178+ value : [ "<test>" , "<fix>" , "<success>" ] ,
1179+ type : GGUFValueType . ARRAY ,
1180+ subType : GGUFValueType . STRING ,
1181+ } ) ;
1182+
1183+ console . log ( "🎯 RangeError fix verified: buildGgufHeader correctly handles blob slicing" ) ;
10291184 } finally {
10301185 try {
10311186 fs . unlinkSync ( tempFilePath ) ;
@@ -1075,7 +1230,7 @@ describe("gguf", () => {
10751230
10761231 // Test that the new header can be parsed
10771232 const tempFilePath = join ( tmpdir ( ) , `test-build-header-tensors-${ Date . now ( ) } .gguf` ) ;
1078- fs . writeFileSync ( tempFilePath , Buffer . from ( await newHeaderBlob . arrayBuffer ( ) ) ) ;
1233+ fs . writeFileSync ( tempFilePath , new Uint8Array ( await newHeaderBlob . arrayBuffer ( ) ) ) ;
10791234
10801235 try {
10811236 const { typedMetadata : parsedMetadata , tensorInfos : parsedTensorInfos } = await gguf ( tempFilePath , {
0 commit comments