@@ -49,6 +49,25 @@ export interface SearchConfig {
4949 contextLines : number ;
5050}
5151
52+ export type RerankerProvider = "cohere" | "jina" | "custom" ;
53+
54+ export interface RerankerConfig {
55+ /** Whether to enable reranking. Default: false */
56+ enabled : boolean ;
57+ /** Provider shortcut for hosted rerank APIs. Use 'custom' to provide only baseUrl. */
58+ provider : RerankerProvider ;
59+ /** Model name for reranking */
60+ model : string ;
61+ /** Base URL of the rerank API endpoint */
62+ baseUrl : string ;
63+ /** API key for the rerank service */
64+ apiKey ?: string ;
65+ /** Number of top documents to rerank */
66+ topN : number ;
67+ /** Request timeout in milliseconds */
68+ timeoutMs : number ;
69+ }
70+
5271export type LogLevel = "error" | "warn" | "info" | "debug" ;
5372
5473export interface DebugConfig {
@@ -83,21 +102,6 @@ export interface CustomProviderConfig {
83102 max_batch_size ?: number ;
84103}
85104
86- export interface RerankerConfig {
87- /** Whether to enable reranking. Default: false */
88- enabled : boolean ;
89- /** Base URL of the rerank API endpoint (e.g. "https://api.siliconflow.cn/v1") */
90- baseUrl : string ;
91- /** Model name for reranking (e.g. "BAAI/bge-reranker-v2-m3") */
92- model : string ;
93- /** API key for the rerank service */
94- apiKey ?: string ;
95- /** Number of top documents to rerank. Default: 20 */
96- topN ?: number ;
97- /** Request timeout in milliseconds. Default: 30000 */
98- timeoutMs ?: number ;
99- }
100-
101105export interface CodebaseIndexConfig {
102106 embeddingProvider : EmbeddingProvider | 'custom' | 'auto' ;
103107 embeddingModel ?: EmbeddingModelName ;
@@ -123,7 +127,7 @@ export type ParsedCodebaseIndexConfig = CodebaseIndexConfig & {
123127 indexing : IndexingConfig ;
124128 search : SearchConfig ;
125129 debug : DebugConfig ;
126- reranker : RerankerConfig ;
130+ reranker ? : RerankerConfig ;
127131 knowledgeBases : string [ ] ;
128132 additionalInclude : string [ ] ;
129133} ;
@@ -164,6 +168,21 @@ function isValidFusionStrategy(value: unknown): value is SearchConfig["fusionStr
164168 return value === "weighted" || value === "rrf" ;
165169}
166170
171+ function isValidRerankerProvider ( value : unknown ) : value is RerankerProvider {
172+ return value === "cohere" || value === "jina" || value === "custom" ;
173+ }
174+
175+ function getDefaultRerankerBaseUrl ( provider : RerankerProvider ) : string {
176+ switch ( provider ) {
177+ case "cohere" :
178+ return "https://api.cohere.ai/v1" ;
179+ case "jina" :
180+ return "https://api.jina.ai/v1" ;
181+ case "custom" :
182+ return "" ;
183+ }
184+ }
185+
167186function getDefaultDebugConfig ( ) : DebugConfig {
168187 return {
169188 enabled : false ,
@@ -177,16 +196,6 @@ function getDefaultDebugConfig(): DebugConfig {
177196 } ;
178197}
179198
180- function getDefaultRerankerConfig ( ) : RerankerConfig {
181- return {
182- enabled : false ,
183- baseUrl : "https://api.siliconflow.cn/v1" ,
184- model : "BAAI/bge-reranker-v2-m3" ,
185- topN : 20 ,
186- timeoutMs : 30000 ,
187- } ;
188- }
189-
190199const VALID_SCOPES : IndexScope [ ] = [ "project" , "global" ] ;
191200const VALID_LOG_LEVELS : LogLevel [ ] = [ "error" , "warn" , "info" , "debug" ] ;
192201
@@ -282,17 +291,6 @@ export function parseConfig(raw: unknown): ParsedCodebaseIndexConfig {
282291 metrics : typeof rawDebug . metrics === "boolean" ? rawDebug . metrics : defaultDebug . metrics ,
283292 } ;
284293
285- const defaultReranker = getDefaultRerankerConfig ( ) ;
286- const rawReranker = ( input . reranker && typeof input . reranker === "object" ? input . reranker : { } ) as Record < string , unknown > ;
287- const reranker : RerankerConfig = {
288- enabled : typeof rawReranker . enabled === "boolean" ? rawReranker . enabled : defaultReranker . enabled ,
289- baseUrl : typeof rawReranker . baseUrl === "string" ? rawReranker . baseUrl . trim ( ) . replace ( / \/ + $ / , '' ) : defaultReranker . baseUrl ,
290- model : typeof rawReranker . model === "string" ? rawReranker . model : defaultReranker . model ,
291- apiKey : getResolvedString ( rawReranker . apiKey , "$root.reranker.apiKey" ) ,
292- topN : typeof rawReranker . topN === "number" ? Math . max ( 1 , Math . min ( 200 , Math . floor ( rawReranker . topN ) ) ) : defaultReranker . topN ,
293- timeoutMs : typeof rawReranker . timeoutMs === "number" ? Math . max ( 1000 , rawReranker . timeoutMs ) : defaultReranker . timeoutMs ,
294- } ;
295-
296294 const rawKnowledgeBases = input . knowledgeBases ;
297295 const knowledgeBases : string [ ] = isStringArray ( rawKnowledgeBases )
298296 ? rawKnowledgeBases . filter ( p => typeof p === "string" && p . trim ( ) . length > 0 ) . map ( p => p . trim ( ) )
@@ -306,6 +304,7 @@ export function parseConfig(raw: unknown): ParsedCodebaseIndexConfig {
306304 let embeddingProvider : EmbeddingProvider | 'custom' | 'auto' ;
307305 let embeddingModel : EmbeddingModelName | undefined = undefined ;
308306 let customProvider : CustomProviderConfig | undefined = undefined ;
307+ let reranker : RerankerConfig | undefined = undefined ;
309308
310309 if ( embeddingProviderValue === 'custom' ) {
311310 embeddingProvider = 'custom' ;
@@ -359,6 +358,39 @@ export function parseConfig(raw: unknown): ParsedCodebaseIndexConfig {
359358 embeddingProvider = 'auto' ;
360359 }
361360
361+ const rawReranker = ( input . reranker && typeof input . reranker === "object"
362+ ? input . reranker
363+ : { } ) as Record < string , unknown > ;
364+ const rerankerEnabled = typeof rawReranker . enabled === "boolean" ? rawReranker . enabled : false ;
365+ if ( rerankerEnabled ) {
366+ const provider = isValidRerankerProvider ( rawReranker . provider ) ? rawReranker . provider : "custom" ;
367+ const model = getResolvedString ( rawReranker . model , "$root.reranker.model" ) ;
368+ if ( ! model || model . trim ( ) . length === 0 ) {
369+ throw new Error ( "reranker is enabled but reranker.model is missing or invalid." ) ;
370+ }
371+
372+ const configuredBaseUrl = getResolvedString ( rawReranker . baseUrl , "$root.reranker.baseUrl" ) ;
373+ const baseUrl = configuredBaseUrl ?. trim ( ) || getDefaultRerankerBaseUrl ( provider ) ;
374+ if ( baseUrl . length === 0 ) {
375+ throw new Error ( "reranker is enabled but reranker.baseUrl is missing or invalid for provider 'custom'." ) ;
376+ }
377+
378+ const apiKey = getResolvedString ( rawReranker . apiKey , "$root.reranker.apiKey" ) ;
379+ if ( ( provider === "cohere" || provider === "jina" ) && ( ! apiKey || apiKey . trim ( ) . length === 0 ) ) {
380+ throw new Error ( `reranker provider '${ provider } ' requires reranker.apiKey when enabled.` ) ;
381+ }
382+
383+ reranker = {
384+ enabled : true ,
385+ provider,
386+ model : model . trim ( ) ,
387+ baseUrl : baseUrl . replace ( / \/ + $ / , "" ) ,
388+ apiKey : apiKey ?. trim ( ) || undefined ,
389+ topN : typeof rawReranker . topN === "number" ? Math . min ( 50 , Math . max ( 1 , Math . floor ( rawReranker . topN ) ) ) : 15 ,
390+ timeoutMs : typeof rawReranker . timeoutMs === "number" ? Math . max ( 1000 , Math . floor ( rawReranker . timeoutMs ) ) : 10000 ,
391+ } ;
392+ }
393+
362394 return {
363395 embeddingProvider,
364396 embeddingModel,
0 commit comments