@@ -12,6 +12,68 @@ export type CatalogDiscoveryState =
1212 | { readonly status : "unavailable" ; readonly message : string }
1313 | { readonly status : "malformed" ; readonly message : string } ;
1414
15+ function catalogErrorMessage ( error : unknown ) : string {
16+ return error instanceof Error ? error . message : String ( error ) ;
17+ }
18+
19+ function declaredCatalogBytes ( response : Response ) : number | undefined {
20+ const raw = response . headers . get ( "content-length" ) ;
21+ if ( raw === null || raw . length === 0 ) return undefined ;
22+ const n = Number ( raw ) ;
23+ if ( ! Number . isFinite ( n ) || n < 0 ) return undefined ;
24+ return n ;
25+ }
26+
27+ async function readBoundedCatalogText (
28+ response : Response ,
29+ args : { readonly maxBytes : number ; readonly oversizeMessage : string } ,
30+ ) : Promise <
31+ | { readonly ok : true ; readonly text : string }
32+ | { readonly ok : false ; readonly message : string }
33+ > {
34+ const declared = declaredCatalogBytes ( response ) ;
35+ if ( declared !== undefined && declared > args . maxBytes ) {
36+ await response . body ?. cancel ( ) . catch ( ( ) => undefined ) ;
37+ return { ok : false , message : args . oversizeMessage } ;
38+ }
39+
40+ try {
41+ const body = response . body ;
42+ if ( body === null ) {
43+ const text = await response . text ( ) ;
44+ if ( new TextEncoder ( ) . encode ( text ) . byteLength > args . maxBytes ) {
45+ return { ok : false , message : args . oversizeMessage } ;
46+ }
47+ return { ok : true , text } ;
48+ }
49+
50+ const reader = body . getReader ( ) ;
51+ const chunks : Uint8Array [ ] = [ ] ;
52+ let total = 0 ;
53+ for ( ; ; ) {
54+ const { done, value } = await reader . read ( ) ;
55+ if ( done ) break ;
56+ if ( value === undefined ) continue ;
57+ total += value . byteLength ;
58+ if ( total > args . maxBytes ) {
59+ await reader . cancel ( ) . catch ( ( ) => undefined ) ;
60+ return { ok : false , message : args . oversizeMessage } ;
61+ }
62+ chunks . push ( value ) ;
63+ }
64+
65+ const buffer = new Uint8Array ( total ) ;
66+ let offset = 0 ;
67+ for ( const chunk of chunks ) {
68+ buffer . set ( chunk , offset ) ;
69+ offset += chunk . byteLength ;
70+ }
71+ return { ok : true , text : new TextDecoder ( ) . decode ( buffer ) } ;
72+ } catch ( error ) {
73+ return { ok : false , message : catalogErrorMessage ( error ) } ;
74+ }
75+ }
76+
1577export function createBoundedModelCatalog ( args : {
1678 baseURL : string ;
1779 seedIds : readonly string [ ] ;
@@ -32,14 +94,6 @@ export function createBoundedModelCatalog(args: {
3294 let inflight : Promise < readonly string [ ] > | undefined ;
3395 let snapshot : readonly string [ ] | undefined ;
3496
35- function declaredCatalogBytes ( response : Response ) : number | undefined {
36- const raw = response . headers . get ( "content-length" ) ;
37- if ( raw === null || raw . length === 0 ) return undefined ;
38- const n = Number ( raw ) ;
39- if ( ! Number . isFinite ( n ) || n < 0 ) return undefined ;
40- return n ;
41- }
42-
4397 function oversizeMessage ( kind : "bytes" | "models" ) : string {
4498 if ( kind === "bytes" ) {
4599 return `${ catalogLabel } catalog exceeds ${ String ( maxBytes ) } bytes` ;
@@ -53,66 +107,16 @@ export function createBoundedModelCatalog(args: {
53107 | { readonly ok : true ; readonly value : unknown }
54108 | { readonly ok : false ; readonly message : string }
55109 > {
56- const declared = declaredCatalogBytes ( response ) ;
57- if ( declared !== undefined && declared > maxBytes ) {
58- await response . body ?. cancel ( ) . catch ( ( ) => undefined ) ;
59- return { ok : false , message : oversizeMessage ( "bytes" ) } ;
60- }
61-
62- const body = response . body ;
63- if ( body === null ) {
64- try {
65- const text = await response . text ( ) ;
66- if ( new TextEncoder ( ) . encode ( text ) . byteLength > maxBytes ) {
67- return { ok : false , message : oversizeMessage ( "bytes" ) } ;
68- }
69- const value : unknown = JSON . parse ( text ) ;
70- return { ok : true , value } ;
71- } catch ( error ) {
72- return {
73- ok : false ,
74- message : error instanceof Error ? error . message : String ( error ) ,
75- } ;
76- }
77- }
78-
79- const reader = body . getReader ( ) ;
80- const chunks : Uint8Array [ ] = [ ] ;
81- let total = 0 ;
110+ const text = await readBoundedCatalogText ( response , {
111+ maxBytes,
112+ oversizeMessage : oversizeMessage ( "bytes" ) ,
113+ } ) ;
114+ if ( ! text . ok ) return text ;
82115 try {
83- for ( ; ; ) {
84- const { done, value } = await reader . read ( ) ;
85- if ( done ) break ;
86- if ( value === undefined ) continue ;
87- total += value . byteLength ;
88- if ( total > maxBytes ) {
89- await reader . cancel ( ) . catch ( ( ) => undefined ) ;
90- return { ok : false , message : oversizeMessage ( "bytes" ) } ;
91- }
92- chunks . push ( value ) ;
93- }
94- } catch ( error ) {
95- return {
96- ok : false ,
97- message : error instanceof Error ? error . message : String ( error ) ,
98- } ;
99- }
100-
101- const buffer = new Uint8Array ( total ) ;
102- let offset = 0 ;
103- for ( const chunk of chunks ) {
104- buffer . set ( chunk , offset ) ;
105- offset += chunk . byteLength ;
106- }
107-
108- try {
109- const value : unknown = JSON . parse ( new TextDecoder ( ) . decode ( buffer ) ) ;
116+ const value : unknown = JSON . parse ( text . text ) ;
110117 return { ok : true , value } ;
111118 } catch ( error ) {
112- return {
113- ok : false ,
114- message : error instanceof Error ? error . message : String ( error ) ,
115- } ;
119+ return { ok : false , message : catalogErrorMessage ( error ) } ;
116120 }
117121 }
118122
@@ -130,7 +134,7 @@ export function createBoundedModelCatalog(args: {
130134 } catch ( error ) {
131135 return {
132136 status : "unavailable" ,
133- message : error instanceof Error ? error . message : String ( error ) ,
137+ message : catalogErrorMessage ( error ) ,
134138 } ;
135139 }
136140
0 commit comments