@@ -44,6 +44,21 @@ describe("lmstudio-models", () => {
4444 }
4545 return JSON . parse ( init . body ) as unknown ;
4646 } ;
47+ // The model fetch/load helpers now read bodies through the shared byte-capped
48+ // reader, so success-path mocks must be real Response objects with a body
49+ // stream rather than bare `{ ok, json }` placeholders.
50+ const jsonResponse = ( payload : unknown , init ?: ResponseInit ) : Response =>
51+ new Response ( JSON . stringify ( payload ) , {
52+ status : 200 ,
53+ headers : { "content-type" : "application/json" } ,
54+ ...init ,
55+ } ) ;
56+ const malformedJsonResponse = ( init ?: ResponseInit ) : Response =>
57+ new Response ( "{ this is not valid json" , {
58+ status : 200 ,
59+ headers : { "content-type" : "application/json" } ,
60+ ...init ,
61+ } ) ;
4762 const cancelTrackedResponse = (
4863 text : string ,
4964 init : ResponseInit ,
@@ -74,31 +89,25 @@ describe("lmstudio-models", () => {
7489 } ) =>
7590 vi . fn ( async ( url : string | URL , init ?: RequestInit ) => {
7691 const key = params ?. key ?? "qwen3-8b-instruct" ;
92+ void init ;
7793 if ( String ( url ) . endsWith ( "/api/v1/models" ) ) {
78- return {
79- ok : true ,
80- json : async ( ) => ( {
81- models : [
82- {
83- type : "llm" ,
84- key,
85- max_context_length : params ?. maxContextLength ,
86- variants : params ?. variants ,
87- selected_variant : params ?. selectedVariant ,
88- loaded_instances : params ?. loadedContextLength
89- ? [ { id : "inst-1" , config : { context_length : params . loadedContextLength } } ]
90- : [ ] ,
91- } ,
92- ] ,
93- } ) ,
94- } ;
94+ return jsonResponse ( {
95+ models : [
96+ {
97+ type : "llm" ,
98+ key,
99+ max_context_length : params ?. maxContextLength ,
100+ variants : params ?. variants ,
101+ selected_variant : params ?. selectedVariant ,
102+ loaded_instances : params ?. loadedContextLength
103+ ? [ { id : "inst-1" , config : { context_length : params . loadedContextLength } } ]
104+ : [ ] ,
105+ } ,
106+ ] ,
107+ } ) ;
95108 }
96109 if ( String ( url ) . endsWith ( "/api/v1/models/load" ) ) {
97- return {
98- ok : true ,
99- json : async ( ) => ( { status : "loaded" } ) ,
100- requestInit : init ,
101- } ;
110+ return jsonResponse ( { status : "loaded" } ) ;
102111 }
103112 throw new Error ( `Unexpected fetch URL: ${ String ( url ) } ` ) ;
104113 } ) ;
@@ -296,9 +305,8 @@ describe("lmstudio-models", () => {
296305 } ) ;
297306
298307 it ( "discovers llm models and maps metadata" , async ( ) => {
299- const fetchMock = vi . fn ( async ( _url : string | URL , _init ?: RequestInit ) => ( {
300- ok : true ,
301- json : async ( ) => ( {
308+ const fetchMock = vi . fn ( async ( _url : string | URL , _init ?: RequestInit ) =>
309+ jsonResponse ( {
302310 models : [
303311 {
304312 type : "llm" ,
@@ -330,7 +338,7 @@ describe("lmstudio-models", () => {
330338 } ,
331339 ] ,
332340 } ) ,
333- } ) ) ;
341+ ) ;
334342
335343 const models = await discoverLmstudioModels ( {
336344 baseUrl : "http://localhost:1234/v1" ,
@@ -386,13 +394,7 @@ describe("lmstudio-models", () => {
386394 } ) ;
387395
388396 it ( "reports malformed model list JSON with an owned error" , async ( ) => {
389- const fetchMock = vi . fn ( async ( ) => ( {
390- ok : true ,
391- status : 200 ,
392- json : async ( ) => {
393- throw new SyntaxError ( "bad json" ) ;
394- } ,
395- } ) ) ;
397+ const fetchMock = vi . fn ( async ( ) => malformedJsonResponse ( ) ) ;
396398
397399 const result = await fetchLmstudioModels ( {
398400 baseUrl : "http://localhost:1234/v1" ,
@@ -405,11 +407,7 @@ describe("lmstudio-models", () => {
405407
406408 it ( "reports wrong-shaped model list payloads with owned errors" , async ( ) => {
407409 for ( const payload of [ [ ] , { models : { } } , { models : [ null ] } ] ) {
408- const fetchMock = vi . fn ( async ( ) => ( {
409- ok : true ,
410- status : 200 ,
411- json : async ( ) => payload ,
412- } ) ) ;
410+ const fetchMock = vi . fn ( async ( ) => jsonResponse ( payload ) ) ;
413411
414412 const result = await fetchLmstudioModels ( {
415413 baseUrl : "http://localhost:1234/v1" ,
@@ -424,12 +422,9 @@ describe("lmstudio-models", () => {
424422 it ( "caps oversized direct fetch timeouts before discovering models" , async ( ) => {
425423 const timeoutController = new AbortController ( ) ;
426424 const timeoutSpy = vi . spyOn ( AbortSignal , "timeout" ) . mockReturnValue ( timeoutController . signal ) ;
427- const fetchMock = vi . fn ( async ( _url : string | URL , init ?: RequestInit ) => ( {
428- ok : true ,
429- status : 200 ,
430- requestInit : init ,
431- json : async ( ) => ( { models : [ ] } ) ,
432- } ) ) ;
425+ const fetchMock = vi . fn ( async ( _url : string | URL , _init ?: RequestInit ) =>
426+ jsonResponse ( { models : [ ] } ) ,
427+ ) ;
433428
434429 const result = await fetchLmstudioModels ( {
435430 baseUrl : "http://localhost:1234/v1" ,
@@ -521,20 +516,17 @@ describe("lmstudio-models", () => {
521516 const variantKey = `${ canonicalKey } @q4_k_m` ;
522517 const fetchMock = vi . fn ( async ( url : string | URL ) => {
523518 if ( String ( url ) . endsWith ( "/api/v1/models" ) ) {
524- return {
525- ok : true ,
526- json : async ( ) => ( {
527- models : [
528- {
529- type : "llm" ,
530- key : canonicalKey ,
531- variants : [ variantKey ] ,
532- selected_variant : variantKey ,
533- loaded_instances : [ ] ,
534- } ,
535- ] ,
536- } ) ,
537- } ;
519+ return jsonResponse ( {
520+ models : [
521+ {
522+ type : "llm" ,
523+ key : canonicalKey ,
524+ variants : [ variantKey ] ,
525+ selected_variant : variantKey ,
526+ loaded_instances : [ ] ,
527+ } ,
528+ ] ,
529+ } ) ;
538530 }
539531 if ( String ( url ) . endsWith ( "/api/v1/models/load" ) ) {
540532 return new Response ( "load failed" , { status : 503 } ) ;
@@ -575,20 +567,12 @@ describe("lmstudio-models", () => {
575567 it ( "reports malformed model load JSON with an owned error" , async ( ) => {
576568 const fetchMock = vi . fn ( async ( url : string | URL ) => {
577569 if ( String ( url ) . endsWith ( "/api/v1/models" ) ) {
578- return {
579- ok : true ,
580- json : async ( ) => ( {
581- models : [ { type : "llm" , key : "qwen3-8b-instruct" , loaded_instances : [ ] } ] ,
582- } ) ,
583- } ;
570+ return jsonResponse ( {
571+ models : [ { type : "llm" , key : "qwen3-8b-instruct" , loaded_instances : [ ] } ] ,
572+ } ) ;
584573 }
585574 if ( String ( url ) . endsWith ( "/api/v1/models/load" ) ) {
586- return {
587- ok : true ,
588- json : async ( ) => {
589- throw new SyntaxError ( "bad json" ) ;
590- } ,
591- } ;
575+ return malformedJsonResponse ( ) ;
592576 }
593577 throw new Error ( `Unexpected fetch URL: ${ String ( url ) } ` ) ;
594578 } ) ;
@@ -599,7 +583,53 @@ describe("lmstudio-models", () => {
599583 baseUrl : "http://localhost:1234/v1" ,
600584 modelKey : "qwen3-8b-instruct" ,
601585 } ) ,
602- ) . rejects . toThrow ( "LM Studio model load returned malformed JSON" ) ;
586+ ) . rejects . toThrow ( "LM Studio model load: malformed JSON response" ) ;
587+ } ) ;
588+
589+ it ( "bounds oversized model load success bodies" , async ( ) => {
590+ // A misbehaving server may stream an unbounded success JSON body; the load
591+ // path must stop reading at the byte cap instead of buffering it all.
592+ let canceled = false ;
593+ let bytesEmitted = 0 ;
594+ const oversizedStream = new ReadableStream < Uint8Array > ( {
595+ pull ( controller ) {
596+ // Far exceeds the 16 MiB provider JSON cap if read to completion.
597+ if ( bytesEmitted >= 32 * 1024 * 1024 ) {
598+ controller . close ( ) ;
599+ return ;
600+ }
601+ bytesEmitted += 64 * 1024 ;
602+ controller . enqueue ( new Uint8Array ( 64 * 1024 ) . fill ( 0x61 ) ) ;
603+ } ,
604+ cancel ( ) {
605+ canceled = true ;
606+ } ,
607+ } ) ;
608+ const fetchMock = vi . fn ( async ( url : string | URL ) => {
609+ if ( String ( url ) . endsWith ( "/api/v1/models" ) ) {
610+ return jsonResponse ( {
611+ models : [ { type : "llm" , key : "qwen3-8b-instruct" , loaded_instances : [ ] } ] ,
612+ } ) ;
613+ }
614+ if ( String ( url ) . endsWith ( "/api/v1/models/load" ) ) {
615+ return new Response ( oversizedStream , {
616+ status : 200 ,
617+ headers : { "content-type" : "application/json" } ,
618+ } ) ;
619+ }
620+ throw new Error ( `Unexpected fetch URL: ${ String ( url ) } ` ) ;
621+ } ) ;
622+ vi . stubGlobal ( "fetch" , asFetch ( fetchMock ) ) ;
623+
624+ const error = await ensureLmstudioModelLoaded ( {
625+ baseUrl : "http://localhost:1234/v1" ,
626+ modelKey : "qwen3-8b-instruct" ,
627+ } ) . catch ( ( caught : unknown ) => caught ) ;
628+
629+ expect ( error ) . toBeInstanceOf ( Error ) ;
630+ expect ( ( error as Error ) . message ) . toMatch ( / J S O N r e s p o n s e e x c e e d s \d + b y t e s / ) ;
631+ expect ( canceled ) . toBe ( true ) ;
632+ expect ( bytesEmitted ) . toBeLessThan ( 32 * 1024 * 1024 ) ;
603633 } ) ;
604634
605635 it ( "bounds model load error bodies" , async ( ) => {
@@ -608,12 +638,9 @@ describe("lmstudio-models", () => {
608638 const textSpy = vi . spyOn ( tracked . response , "text" ) . mockRejectedValue ( new Error ( "unbounded" ) ) ;
609639 const fetchMock = vi . fn ( async ( url : string | URL ) => {
610640 if ( String ( url ) . endsWith ( "/api/v1/models" ) ) {
611- return {
612- ok : true ,
613- json : async ( ) => ( {
614- models : [ { type : "llm" , key : "qwen3-8b-instruct" , loaded_instances : [ ] } ] ,
615- } ) ,
616- } ;
641+ return jsonResponse ( {
642+ models : [ { type : "llm" , key : "qwen3-8b-instruct" , loaded_instances : [ ] } ] ,
643+ } ) ;
617644 }
618645 if ( String ( url ) . endsWith ( "/api/v1/models/load" ) ) {
619646 return tracked . response ;
0 commit comments