@@ -28,6 +28,10 @@ import type {
2828
2929const DEFAULT_COLLECTION = "loopover" ;
3030const DEFAULT_DIM = 1024 ; // bge-m3 / mxbai-embed-large (1024-d); set QDRANT_DIM to override
31+ // Per-request ceiling for every Qdrant REST call (#7072). This is a self-host Node.js adapter with no
32+ // platform-level subrequest limit, so an unresponsive/partitioned Qdrant instance would otherwise hang these
33+ // calls indefinitely -- matching the bounded-fetch convention of the other self-host adapters (e.g. ai.ts).
34+ const QDRANT_FETCH_TIMEOUT_MS = 15_000 ;
3135
3236interface QdrantSearchResult {
3337 result : Array < { id : string ; score : number ; payload : Record < string , unknown > } > ;
@@ -73,12 +77,16 @@ export async function initQdrantCollection(
7377 method : "PUT" ,
7478 headers : qdrantHeaders ( ) ,
7579 body : JSON . stringify ( { vectors : { size : dim , distance : "Cosine" } } ) ,
80+ signal : AbortSignal . timeout ( QDRANT_FETCH_TIMEOUT_MS ) ,
7681 } ) ;
7782 if ( ! res . ok && res . status !== 409 ) {
7883 throw new Error ( `Qdrant collection init failed: HTTP ${ res . status } ` ) ;
7984 }
8085 if ( res . status === 409 ) {
81- const existing = await fetch ( `${ base } /collections/${ collection } ` , { headers : qdrantHeaders ( ) } ) ;
86+ const existing = await fetch ( `${ base } /collections/${ collection } ` , {
87+ headers : qdrantHeaders ( ) ,
88+ signal : AbortSignal . timeout ( QDRANT_FETCH_TIMEOUT_MS ) ,
89+ } ) ;
8290 if ( ! existing . ok ) throw new Error ( `Qdrant collection lookup failed: HTTP ${ existing . status } ` ) ;
8391 const info = ( await existing . json ( ) ) as QdrantCollectionInfo ;
8492 const existingDim = info . result . config . params . vectors . size ;
@@ -103,6 +111,7 @@ export function createQdrantVectorize(url: string, collection = DEFAULT_COLLECTI
103111 method : "PUT" ,
104112 headers : qdrantHeaders ( ) ,
105113 body : JSON . stringify ( { points } ) ,
114+ signal : AbortSignal . timeout ( QDRANT_FETCH_TIMEOUT_MS ) ,
106115 } ) ;
107116 if ( ! res . ok ) {
108117 incr ( "loopover_qdrant_errors_total" , { op : "upsert" } ) ;
@@ -123,6 +132,7 @@ export function createQdrantVectorize(url: string, collection = DEFAULT_COLLECTI
123132 method : "POST" ,
124133 headers : qdrantHeaders ( ) ,
125134 body : JSON . stringify ( body ) ,
135+ signal : AbortSignal . timeout ( QDRANT_FETCH_TIMEOUT_MS ) ,
126136 } ) ;
127137 } catch {
128138 // Qdrant unreachable — degrade gracefully (RAG returns no context rather than crashing)
@@ -150,6 +160,7 @@ export function createQdrantVectorize(url: string, collection = DEFAULT_COLLECTI
150160 method : "POST" ,
151161 headers : qdrantHeaders ( ) ,
152162 body : JSON . stringify ( { points } ) ,
163+ signal : AbortSignal . timeout ( QDRANT_FETCH_TIMEOUT_MS ) ,
153164 } ) ;
154165 if ( ! res . ok ) {
155166 incr ( "loopover_qdrant_errors_total" , { op : "delete" } ) ;
0 commit comments